From f47634fec43c737141536fe997b391878d74751d Mon Sep 17 00:00:00 2001 From: Petr Barta Date: Tue, 17 Sep 2024 13:52:49 +0200 Subject: [PATCH 001/408] Pfda session extension --- .../session-expiration-dialog.component.ts | 12 +- .../session-expiration.component.ts | 177 +++++++++++------- src/app/core/config/config.model.ts | 3 +- src/app/fda/fda.module.ts | 16 -- .../fda/service/sso-refresh.service.spec.ts | 12 -- src/app/fda/service/sso-refresh.service.ts | 87 --------- 6 files changed, 116 insertions(+), 191 deletions(-) delete mode 100644 src/app/fda/service/sso-refresh.service.spec.ts delete mode 100644 src/app/fda/service/sso-refresh.service.ts diff --git a/src/app/core/auth/session-expiration/session-expiration-dialog/session-expiration-dialog.component.ts b/src/app/core/auth/session-expiration/session-expiration-dialog/session-expiration-dialog.component.ts index 3dbae841e..da96f8478 100644 --- a/src/app/core/auth/session-expiration/session-expiration-dialog/session-expiration-dialog.component.ts +++ b/src/app/core/auth/session-expiration/session-expiration-dialog/session-expiration-dialog.component.ts @@ -1,9 +1,8 @@ import { Component, OnInit, Inject } from '@angular/core'; import { Router } from '@angular/router'; import { HttpClient } from '@angular/common/http'; -import { ConfigService, SessionExpirationWarning } from '@gsrs-core/config'; -import { MatDialog, MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog'; -import { AnyNsRecord } from 'dns'; +import { SessionExpirationWarning } from '@gsrs-core/config'; +import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog'; @Component({ selector: 'app-session-expiration-dialog', @@ -49,11 +48,10 @@ export class SessionExpirationDialogComponent implements OnInit { if (this.timeRemainingSeconds > 0) { const remainingMinutes = Math.floor(this.timeRemainingSeconds / 60); - const reminaingSeconds = String(this.timeRemainingSeconds % 60).padStart(2, '0'); + const remainingSeconds = String(this.timeRemainingSeconds % 60).padStart(2, '0'); this.dialogTitle = "Session Ending Soon" - this.dialogMessage = `You will be logged out in ${remainingMinutes}:${reminaingSeconds}` - } - else { + this.dialogMessage = `You will be logged out in ${remainingMinutes}:${remainingSeconds}` + } else { this.dialogTitle = "Session Ended" this.dialogMessage = "Your session has expired, please login again." } diff --git a/src/app/core/auth/session-expiration/session-expiration.component.ts b/src/app/core/auth/session-expiration/session-expiration.component.ts index 14fb287a5..bafcaaf28 100644 --- a/src/app/core/auth/session-expiration/session-expiration.component.ts +++ b/src/app/core/auth/session-expiration/session-expiration.component.ts @@ -1,12 +1,12 @@ -import { Router, Event as NavigationEvent, NavigationStart } from '@angular/router'; +import { Router } from '@angular/router'; import { Component, OnInit } from '@angular/core'; import { OverlayContainer } from '@angular/cdk/overlay'; import { HttpClient } from '@angular/common/http'; import { ConfigService, SessionExpirationWarning } from '@gsrs-core/config'; import { AuthService } from '../auth.service'; import { SessionExpirationDialogComponent } from './session-expiration-dialog/session-expiration-dialog.component' -import { Subscription } from 'rxjs'; import { MatDialog } from '@angular/material/dialog'; +import { UtilsService } from "@gsrs-core/utils"; @Component({ selector: 'app-session-expiration', @@ -16,8 +16,13 @@ export class SessionExpirationComponent implements OnInit { sessionExpirationWarning: SessionExpirationWarning = null; sessionExpiringAt: number; private overlayContainer: HTMLElement; - private subscriptions: Array = []; - private expirationTimer: any; + private refreshInterval: any; + private activityRefreshInterval: any; + private userActive: boolean = false; + private baseHref: string = '/ginas/app/'; + + private static instance?: SessionExpirationComponent = undefined; + private static sessionExpirationCheckInterval = null; constructor( private router: Router, @@ -25,72 +30,118 @@ export class SessionExpirationComponent implements OnInit { private authService: AuthService, private http: HttpClient, private dialog: MatDialog, - private overlayContainerService: OverlayContainer + private overlayContainerService: OverlayContainer, + private utilsService: UtilsService ) { + if (SessionExpirationComponent.instance !== undefined) { + return SessionExpirationComponent.instance; + } this.sessionExpirationWarning = configService.configData.sessionExpirationWarning; this.overlayContainer = this.overlayContainerService.getContainerElement(); } ngOnInit() { - // If SessionExpirationWarning is not found in configData, the intervals are never set - // and this component is inert - const authSubscription = this.authService.getAuth().subscribe(auth => { - if (this.sessionExpirationWarning) { - if (auth) { - this.resetExpirationTimer(); - } - else { - // User has logged out while timeout is active - this.clearExpirationTimer(); - } + if (SessionExpirationComponent.instance !== undefined) { + return; + } + SessionExpirationComponent.instance = this; + + const homeBaseUrl = this.configService.configData && this.configService.configData.gsrsHomeBaseUrl || null; + if (homeBaseUrl) { + this.baseHref = homeBaseUrl; + } + + this.startSessionTimeoutInterval(); + } + + setup() { + this.configService.afterLoad().then(cd => { + // If enabled in config file, this functionality periodically checks whether there was a user activity (mouse or keyboard) or not + // In case there was some activity, the session is refreshed (otherwise the session is not refreshed and may eventually expire) + if (this.configService.configData.sessionRefreshOnActiveUser) { + const page = document.getElementsByTagName('body')[0]; + page.addEventListener('mousemove', (e) => { + if (e instanceof MouseEvent) { + this.userActive = true; + } + }); + page.addEventListener('keydown', (e) => { + if (e instanceof KeyboardEvent) { + this.userActive = true; + } + }); + clearInterval(this.activityRefreshInterval); + this.activityRefreshInterval = setInterval(() => { + if (this.userActive) { + this.refreshSession(); + this.userActive = false; + } + }, 10000); + } + + if (!this.configService.configData.disableSessionAutoRefresh) { + clearInterval(this.refreshInterval); + this.refreshInterval = setInterval(() => { + this.refreshSession(); + }, 600000); } }); - this.subscriptions.push(authSubscription); - - // This component seems to be destroyed and recreated on route change, so maybe - // the following isn't necessary: - // const routerSubscription = this.router.events.subscribe((event: NavigationEvent) => { - // if (event instanceof NavigationStart && this.expirationTimer) { - // this.extendSession(); - // } - // }); - // this.subscriptions.push(routerSubscription); } - ngOnDestroy() { - this.subscriptions.forEach(subscription => { - subscription.unsubscribe(); - }); - this.clearExpirationTimer(); + refreshSession(): any { + fetch(`${this.baseHref || ''}api/v1/whoami?key=${this.utilsService.newUUID()}`) } - getCurrentTime() { - return Math.floor((new Date()).getTime() / 1000); + startSessionTimeoutInterval() { + this.authService.getAuth().subscribe(auth => { + if (auth != null && this.refreshInterval == null) { + this.setup(); + } else if (auth === null) { + clearInterval(this.refreshInterval); + this.refreshInterval = null; + } + }); + + clearInterval(SessionExpirationComponent.sessionExpirationCheckInterval); + SessionExpirationComponent.sessionExpirationCheckInterval = setInterval(() => { + this.sessionExpiringAt = this.getSessionExpiredAt(); + const currentTime = this.getCurrentTime(); + const sessionTtl = this.sessionExpiringAt - currentTime; + // If session is about to expire in less than 60 seconds, show dialog window + if (sessionTtl > 0 && sessionTtl < 60) { + if (!this.isDialogOpened()) { + this.openDialog(); + } + // Do not automatically (mouse/keyboard event) extend session when the dialog is opened + clearInterval(this.activityRefreshInterval); + } else if (this.sessionExpiringAt !== null && sessionTtl > 0) { + // The session was externally extended (eg. in pfda) -> close the session dialog + if (this.isDialogOpened()) { + this.dialog.closeAll(); + } + } + }, 5000) } - clearExpirationTimer() { - if (this.expirationTimer) { - clearTimeout(this.expirationTimer); - this.expirationTimer = null; + private getCookie(name: string) { + const cookieArr = document.cookie.split(';') + for (let i = 0; i < cookieArr.length; i++) { + const cookiePair = cookieArr[i].split('=') + if (name === cookiePair[0].trim()) { + return decodeURIComponent(cookiePair[1]) + } } + return null } - resetExpirationTimer() { - this.clearExpirationTimer(); - - const currentTime = this.getCurrentTime() - this.sessionExpiringAt = currentTime + this.sessionExpirationWarning.maxSessionDurationMinutes * 60; + private getSessionExpiredAt() { + const cookie = this.getCookie('sessionExpiredAt') + if (!cookie) return null + return parseInt(cookie) + } - const timeRemainingSeconds = this.sessionExpiringAt - currentTime; - const timeBeforeDisplayingDialogMs = (timeRemainingSeconds - 61) * 1000; - if (timeBeforeDisplayingDialogMs > 0) { - this.expirationTimer = setTimeout( () => { - this.openDialog(); - }, timeBeforeDisplayingDialogMs); - } - else { - this.login(); - } + getCurrentTime() { + return Math.floor((new Date()).getTime() / 1000); } openDialog() { @@ -104,27 +155,17 @@ export class SessionExpirationComponent implements OnInit { disableClose: true }); this.overlayContainer.style.zIndex = '1501'; - const dialogSubscription = dialogRef.afterClosed().subscribe(response => { + dialogRef.afterClosed().subscribe(response => { this.overlayContainer.style.zIndex = null; - if (response) { - // Session was extended - this.resetExpirationTimer(); - } + this.startSessionTimeoutInterval(); }); } - extendSession() { - const url = this.sessionExpirationWarning.extendSessionApiUrl; - this.http.get(url).subscribe( - data => { - this.resetExpirationTimer(); - }, - err => { console.log("Error extending session: ", err) }, - () => { } - ); - } - login() { window.location.assign('/login'); } + + isDialogOpened(): boolean { + return this.dialog.openDialogs.length > 0; + } } diff --git a/src/app/core/config/config.model.ts b/src/app/core/config/config.model.ts index b546aca89..6bb4844cc 100644 --- a/src/app/core/config/config.model.ts +++ b/src/app/core/config/config.model.ts @@ -38,7 +38,8 @@ export interface Config { facetDisplay?: Array; relationshipsVisualizationUri?: string; customToolbarComponent?: string; - disableSessionRefresh?: boolean; + sessionRefreshOnActiveUser?: boolean; + disableSessionAutoRefresh?: boolean; sessionExpirationWarning?: SessionExpirationWarning; disableReferenceDocumentUpload?: boolean; externalSiteWarning?: ExternalSiteWarning; diff --git a/src/app/fda/fda.module.ts b/src/app/fda/fda.module.ts index ad4410cd5..7f83a7751 100644 --- a/src/app/fda/fda.module.ts +++ b/src/app/fda/fda.module.ts @@ -27,7 +27,6 @@ import { SubstanceApplicationMatchListComponent} from './substance-browse/substa import { ApplicationsBrowseComponent } from './application/applications-browse/applications-browse.component'; import { ClinicalTrialsBrowseComponent } from './clinical-trials/clinical-trials-browse/clinical-trials-browse.component'; import { fdaSubstanceCardsFilters } from './substance-details/fda-substance-cards-filters.constant'; -import { SsoRefreshService } from './service/sso-refresh.service'; import { ProductService } from './product/service/product.service'; import { GeneralService} from './service/general.service'; import { ShowApplicationToggleComponent } from './substance-browse/show-application-toggle/show-application-toggle.component'; @@ -57,12 +56,6 @@ const fdaRoutes: Routes = [ } ]; -export function init_sso_refresh_service(ssoService: SsoRefreshService) { - return() => { - ssoService.init(); - }; -} - @NgModule({ imports: [ CommonModule, @@ -100,15 +93,6 @@ export function init_sso_refresh_service(ssoService: SsoRefreshService) { SubstanceCountsComponent, ShowApplicationToggleComponent - ], - providers: [ - SsoRefreshService, - { - provide: APP_INITIALIZER, - useFactory: init_sso_refresh_service, - deps: [SsoRefreshService], - multi: true - } ] }) export class FdaModule { diff --git a/src/app/fda/service/sso-refresh.service.spec.ts b/src/app/fda/service/sso-refresh.service.spec.ts deleted file mode 100644 index ca2f068f5..000000000 --- a/src/app/fda/service/sso-refresh.service.spec.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { TestBed } from '@angular/core/testing'; - -import { SsoRefreshService } from './sso-refresh.service'; - -describe('SsoRefreshService', () => { - beforeEach(() => TestBed.configureTestingModule({})); - - it('should be created', () => { - const service: SsoRefreshService = TestBed.get(SsoRefreshService); - expect(service).toBeTruthy(); - }); -}); diff --git a/src/app/fda/service/sso-refresh.service.ts b/src/app/fda/service/sso-refresh.service.ts deleted file mode 100644 index aa97fb0a4..000000000 --- a/src/app/fda/service/sso-refresh.service.ts +++ /dev/null @@ -1,87 +0,0 @@ -import { Injectable, Inject, PLATFORM_ID, OnDestroy } from '@angular/core'; -import { Router, NavigationExtras, ActivatedRoute } from '@angular/router'; -import { isPlatformBrowser } from '@angular/common'; -import { take } from 'rxjs/operators'; -import { AuthService } from '@gsrs-core/auth'; -import { UtilsService } from '@gsrs-core/utils'; -import { ConfigService } from '@gsrs-core/config/config.service'; - -@Injectable() -export class SsoRefreshService implements OnDestroy { - private iframe: HTMLIFrameElement; - private refreshInterval: any; - private baseHref: string; - private showHeaderBar = 'true'; - - constructor( - @Inject(PLATFORM_ID) private platformId: Object, - private utilsService: UtilsService, - private configService: ConfigService, - private authService: AuthService, - private activatedRoute: ActivatedRoute - ) { - if (isPlatformBrowser(this.platformId)) { - - if (window.location.pathname.indexOf('/ginas/app/ui/') > -1) { - this.baseHref = '/ginas/app/'; - } - } - } - - updateIframe(): any { - if (!this.iframe) { - this.iframe = document.createElement('IFRAME') as HTMLIFrameElement; - this.iframe.title = 'page refresher'; - this.iframe.name = 'refresher'; - this.iframe.style.height = '0'; - this.iframe.style.opacity = '0'; - this.iframe.src = `${this.baseHref || ''}api/v1/whoami?key=${this.utilsService.newUUID()}&noWarningBox=true`; - document.body.appendChild(this.iframe); - } else { - this.iframe.src = `${this.baseHref || ''}api/v1/whoami?key=${this.utilsService.newUUID()}&noWarningBox=true`; - } - } - - setup() { - this.configService.afterLoad().then(cd => { - // Session auto refresh can be explicitly disabled in config file - if (this.configService.configData.disableSessionRefresh) { - return; - } - const homeBaseUrl = this.configService.configData && this.configService.configData.gsrsHomeBaseUrl || null; - if (homeBaseUrl) { - this.baseHref = homeBaseUrl; - this.updateIframe(); - } - clearInterval(this.refreshInterval); - this.refreshInterval = setInterval(() => { - console.log("REFRESHING iFrame"); - this.updateIframe(); - }, 600000); - }); - } - - init(): any { - if(new URLSearchParams(window.location.search).get("noWarningBox") === 'true'){ - //do not do sso refresher recursively - return; - } - if (new URLSearchParams(window.location.search).get("header") === 'false') { - this.setup(); - } else { - this.authService.getAuth().subscribe(auth => { - if (auth != null && this.refreshInterval == null) { - this.setup(); - } else if (auth === null){ - clearInterval(this.refreshInterval); - this.refreshInterval = null; - } - }); - } //else - } - - ngOnDestroy() { - clearInterval(this.refreshInterval); - this.refreshInterval = null; - } -} From 019dfe29e670d37dd61bb384c9b0d2625745a943 Mon Sep 17 00:00:00 2001 From: Petr Barta Date: Wed, 18 Sep 2024 14:29:08 +0200 Subject: [PATCH 002/408] Config sessionRefreshOnActiveUserOnly field --- .../auth/session-expiration/session-expiration.component.ts | 6 ++---- src/app/core/config/config.model.ts | 3 +-- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/src/app/core/auth/session-expiration/session-expiration.component.ts b/src/app/core/auth/session-expiration/session-expiration.component.ts index bafcaaf28..4f8b1fdbd 100644 --- a/src/app/core/auth/session-expiration/session-expiration.component.ts +++ b/src/app/core/auth/session-expiration/session-expiration.component.ts @@ -58,7 +58,7 @@ export class SessionExpirationComponent implements OnInit { this.configService.afterLoad().then(cd => { // If enabled in config file, this functionality periodically checks whether there was a user activity (mouse or keyboard) or not // In case there was some activity, the session is refreshed (otherwise the session is not refreshed and may eventually expire) - if (this.configService.configData.sessionRefreshOnActiveUser) { + if (this.configService.configData.sessionRefreshOnActiveUserOnly) { const page = document.getElementsByTagName('body')[0]; page.addEventListener('mousemove', (e) => { if (e instanceof MouseEvent) { @@ -77,9 +77,7 @@ export class SessionExpirationComponent implements OnInit { this.userActive = false; } }, 10000); - } - - if (!this.configService.configData.disableSessionAutoRefresh) { + } else { clearInterval(this.refreshInterval); this.refreshInterval = setInterval(() => { this.refreshSession(); diff --git a/src/app/core/config/config.model.ts b/src/app/core/config/config.model.ts index 6bb4844cc..00c149ab2 100644 --- a/src/app/core/config/config.model.ts +++ b/src/app/core/config/config.model.ts @@ -38,8 +38,7 @@ export interface Config { facetDisplay?: Array; relationshipsVisualizationUri?: string; customToolbarComponent?: string; - sessionRefreshOnActiveUser?: boolean; - disableSessionAutoRefresh?: boolean; + sessionRefreshOnActiveUserOnly?: boolean; sessionExpirationWarning?: SessionExpirationWarning; disableReferenceDocumentUpload?: boolean; externalSiteWarning?: ExternalSiteWarning; From 53d71dec258d325dedf82837c27ca97e1d051a53 Mon Sep 17 00:00:00 2001 From: Petr Barta Date: Thu, 19 Sep 2024 11:49:06 +0200 Subject: [PATCH 003/408] Session expiration dialog fix --- .../session-expiration.component.ts | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/src/app/core/auth/session-expiration/session-expiration.component.ts b/src/app/core/auth/session-expiration/session-expiration.component.ts index 4f8b1fdbd..e2a55062a 100644 --- a/src/app/core/auth/session-expiration/session-expiration.component.ts +++ b/src/app/core/auth/session-expiration/session-expiration.component.ts @@ -1,11 +1,9 @@ -import { Router } from '@angular/router'; import { Component, OnInit } from '@angular/core'; import { OverlayContainer } from '@angular/cdk/overlay'; -import { HttpClient } from '@angular/common/http'; import { ConfigService, SessionExpirationWarning } from '@gsrs-core/config'; import { AuthService } from '../auth.service'; import { SessionExpirationDialogComponent } from './session-expiration-dialog/session-expiration-dialog.component' -import { MatDialog } from '@angular/material/dialog'; +import { MatDialog, MatDialogRef, MatDialogState } from '@angular/material/dialog'; import { UtilsService } from "@gsrs-core/utils"; @Component({ @@ -20,16 +18,15 @@ export class SessionExpirationComponent implements OnInit { private activityRefreshInterval: any; private userActive: boolean = false; private baseHref: string = '/ginas/app/'; + private extendSessionDialog: MatDialogRef; private static instance?: SessionExpirationComponent = undefined; private static sessionExpirationCheckInterval = null; constructor( - private router: Router, private configService: ConfigService, private authService: AuthService, - private http: HttpClient, - private dialog: MatDialog, + private matDialog: MatDialog, private overlayContainerService: OverlayContainer, private utilsService: UtilsService ) { @@ -115,7 +112,7 @@ export class SessionExpirationComponent implements OnInit { } else if (this.sessionExpiringAt !== null && sessionTtl > 0) { // The session was externally extended (eg. in pfda) -> close the session dialog if (this.isDialogOpened()) { - this.dialog.closeAll(); + this.extendSessionDialog.close(); } } }, 5000) @@ -143,7 +140,7 @@ export class SessionExpirationComponent implements OnInit { } openDialog() { - const dialogRef = this.dialog.open(SessionExpirationDialogComponent, { + this.extendSessionDialog = this.matDialog.open(SessionExpirationDialogComponent, { data: { 'sessionExpirationWarning': this.sessionExpirationWarning, 'sessionExpiringAt': this.sessionExpiringAt @@ -153,7 +150,7 @@ export class SessionExpirationComponent implements OnInit { disableClose: true }); this.overlayContainer.style.zIndex = '1501'; - dialogRef.afterClosed().subscribe(response => { + this.extendSessionDialog.afterClosed().subscribe(response => { this.overlayContainer.style.zIndex = null; this.startSessionTimeoutInterval(); }); @@ -164,6 +161,6 @@ export class SessionExpirationComponent implements OnInit { } isDialogOpened(): boolean { - return this.dialog.openDialogs.length > 0; + return this.extendSessionDialog && this.extendSessionDialog.getState() === MatDialogState.OPEN; } } From 82cb43a4c61c0e5e31675b6fd086269293ed85bb Mon Sep 17 00:00:00 2001 From: Petr Barta Date: Thu, 10 Oct 2024 15:08:28 +0200 Subject: [PATCH 004/408] PFDA-5656 Substance registration pages do not require pFDA login to access --- src/app/core/auth/auth.service.ts | 49 +++++++++++++++-- .../session-expiration.component.ts | 6 ++- src/app/core/base/base-http.service.ts | 4 ++ src/app/core/base/base.component.html | 4 +- src/app/core/base/base.component.ts | 4 +- src/app/core/config/config.model.ts | 3 +- src/app/core/config/config.pfda.json | 4 +- src/app/core/config/config.service.ts | 3 ++ .../can-register-substance-form.ts | 51 ++++++++++-------- src/app/core/substance/substance.service.ts | 54 +++++++++++++++++-- src/environments/environment.model.ts | 1 + 11 files changed, 144 insertions(+), 39 deletions(-) diff --git a/src/app/core/auth/auth.service.ts b/src/app/core/auth/auth.service.ts index 2d354cdb4..317fb295e 100644 --- a/src/app/core/auth/auth.service.ts +++ b/src/app/core/auth/auth.service.ts @@ -303,16 +303,55 @@ export class AuthService { private fetchAuth(): Observable { return new Observable(observer => { this.configService.afterLoad().then(cd => { - const url = `${(this.configService.configData && this.configService.configData.apiBaseUrl) || '/'}api/v1/`; + const isPfdaVersion = this.configService.configData.isPfdaVersion === true; + const url = isPfdaVersion ? '/api/user' : + `${(this.configService.configData && this.configService.configData.apiBaseUrl) || '/'}api/v1/whoami`; if (this.configService.configData && this.configService.configData.dummyWhoami) { observer.next(this.configService.configData.dummyWhoami); } else { - this.http.get(`${url}whoami`) + this.http.get(url) .subscribe( auth => { - // console.log("Authorized as"); - // console.log(auth); - observer.next(auth); + if (isPfdaVersion) { + // @ts-ignore + const dxuser = auth.user.dxuser; + const pfdaAuth: Auth = { + id: 0, + version: 0, + created: 0, + modified: 0, + deprecated: false, + user: { + id: 0, + version: 0, + created: 0, + modified: 0, + deprecated: false, + username: dxuser, + email: auth.user.email, + admin: auth.user.admin + }, + active: true, + systemAuth: false, + key: 'unused', + identifier: dxuser, + groups: [], + roles: [ + "Query", + "Updater", + "SuperUpdate", + "DataEntry", + "SuperDataEntry" + ], + computedToken: 'unused', + tokenTimeToExpireMS: 9999999999999, + roleQueryOnly: false, + permissions: [] + } + observer.next(pfdaAuth); + } else { + observer.next(auth); + } }, err => { console.log("Authorized error"); diff --git a/src/app/core/auth/session-expiration/session-expiration.component.ts b/src/app/core/auth/session-expiration/session-expiration.component.ts index e2a55062a..1f903f2f1 100644 --- a/src/app/core/auth/session-expiration/session-expiration.component.ts +++ b/src/app/core/auth/session-expiration/session-expiration.component.ts @@ -84,7 +84,11 @@ export class SessionExpirationComponent implements OnInit { } refreshSession(): any { - fetch(`${this.baseHref || ''}api/v1/whoami?key=${this.utilsService.newUUID()}`) + if (this.configService.configData.isPfdaVersion) { + fetch(`${this.configService.configData.pfdaApiBaseUrl}user`) + } else { + fetch(`${this.baseHref || ''}api/v1/whoami?key=${this.utilsService.newUUID()}`) + } } startSessionTimeoutInterval() { diff --git a/src/app/core/base/base-http.service.ts b/src/app/core/base/base-http.service.ts index c8a7db553..fd27e461e 100644 --- a/src/app/core/base/base-http.service.ts +++ b/src/app/core/base/base-http.service.ts @@ -2,12 +2,16 @@ import { ConfigService } from '../config/config.service'; export abstract class BaseHttpService { public apiBaseUrl: string; + public pfdaApiBaseUrl: string = ''; public baseUrl: string; constructor( public configService: ConfigService ) { this.apiBaseUrl = `${(this.configService.configData && this.configService.configData.apiBaseUrl) || '/' }api/v1/`; + if (this.configService.configData.isPfdaVersion && this.configService.configData.pfdaApiBaseUrl) { + this.pfdaApiBaseUrl = this.configService.configData.pfdaApiBaseUrl; + } this.baseUrl = (this.configService.configData && this.configService.configData.apiBaseUrl) || '/'; } } diff --git a/src/app/core/base/base.component.html b/src/app/core/base/base.component.html index 4194f0be6..7355090e3 100644 --- a/src/app/core/base/base.component.html +++ b/src/app/core/base/base.component.html @@ -1,4 +1,4 @@ - + -
+
diff --git a/src/app/core/base/base.component.ts b/src/app/core/base/base.component.ts index 1d12fe9bb..186303abb 100644 --- a/src/app/core/base/base.component.ts +++ b/src/app/core/base/base.component.ts @@ -46,7 +46,7 @@ export class BaseComponent implements OnInit, OnDestroy { appId: string; clasicBaseHref: string; navItems: Array; - customToolbarComponent: string = ''; + isPfdaVersion: boolean = false; canRegister = false; registerNav: Array; searchNav: Array; @@ -75,7 +75,7 @@ export class BaseComponent implements OnInit, OnDestroy { private utilsService: UtilsService, private wildCardService: WildcardService ) { - this.customToolbarComponent = this.configService.configData.customToolbarComponent; + this.isPfdaVersion = this.configService.configData.isPfdaVersion === true; this.wildCardService.wildCardObservable.subscribe((data) => { this.wildCardText = data; }); diff --git a/src/app/core/config/config.model.ts b/src/app/core/config/config.model.ts index 00c149ab2..821401668 100644 --- a/src/app/core/config/config.model.ts +++ b/src/app/core/config/config.model.ts @@ -2,6 +2,7 @@ import { Auth } from "@gsrs-core/auth"; export interface Config { apiBaseUrl?: string; + pfdaApiBaseUrl?: string; gsrsHomeBaseUrl?: string; apiSSG4mBaseUrl?: string; apiUrlDomain?: string; @@ -37,7 +38,7 @@ export interface Config { advancedSearchFacetDisplay?: boolean; facetDisplay?: Array; relationshipsVisualizationUri?: string; - customToolbarComponent?: string; + isPfdaVersion?: boolean; sessionRefreshOnActiveUserOnly?: boolean; sessionExpirationWarning?: SessionExpirationWarning; disableReferenceDocumentUpload?: boolean; diff --git a/src/app/core/config/config.pfda.json b/src/app/core/config/config.pfda.json index dc35f4eb0..54955437b 100644 --- a/src/app/core/config/config.pfda.json +++ b/src/app/core/config/config.pfda.json @@ -544,5 +544,5 @@ "dialogMessage" : "You will be making an API call outside of the precisionFDA boundary. Do you want to continue?" }, "googleAnalyticsId": "", - "customToolbarComponent": "precisionFDA" -} \ No newline at end of file + "isPfdaVersion": true +} diff --git a/src/app/core/config/config.service.ts b/src/app/core/config/config.service.ts index e2a78a6af..43586ac7b 100644 --- a/src/app/core/config/config.service.ts +++ b/src/app/core/config/config.service.ts @@ -47,6 +47,9 @@ export class ConfigService { if (config.apiBaseUrl == null && environment.apiBaseUrl != null) { config.apiBaseUrl = environment.apiBaseUrl; } + if (config.pfdaApiBaseUrl == null && environment.pfdaApiBaseUrl != null) { + config.pfdaApiBaseUrl = environment.pfdaApiBaseUrl; + } if (config.apiBaseUrl.indexOf('//') > -1) { const parts = config.apiBaseUrl.split('/'); config.apiUrlDomain = `${parts[0]}//${parts[2]}`; diff --git a/src/app/core/substance-form/can-register-substance-form.ts b/src/app/core/substance-form/can-register-substance-form.ts index 8f6ce3ba4..a4cad22a6 100644 --- a/src/app/core/substance-form/can-register-substance-form.ts +++ b/src/app/core/substance-form/can-register-substance-form.ts @@ -3,13 +3,15 @@ import { Router, CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot, Navig import { AuthService } from '../auth/auth.service'; import { Observable } from 'rxjs'; import {Role} from '@gsrs-core/auth/auth.model'; +import { ConfigService } from "@gsrs-core/config"; @Injectable() export class CanRegisterSubstanceForm implements CanActivate { constructor( private router: Router, - private authService: AuthService + private authService: AuthService, + private configService: ConfigService ) {} canActivate( @@ -17,27 +19,32 @@ export class CanRegisterSubstanceForm implements CanActivate { state: RouterStateSnapshot ): Observable | Promise | (boolean | UrlTree) { return new Observable(observer => { - this.authService.getAuth().subscribe(auth => { - if (auth) { - this.authService.hasAnyRolesAsync('DataEntry', 'SuperDataEntry').subscribe(response => { - if (response) { - observer.next(true); - observer.complete(); - } else { - observer.next(this.router.parseUrl('/browse-substance')); - observer.complete(); - } - }); - } else { - const navigationExtras: NavigationExtras = { - queryParams: { - path: state.url - } - }; - observer.next(this.router.createUrlTree(['/login'], navigationExtras)); - observer.complete(); - } - }); + if (this.configService.configData.isPfdaVersion) { + observer.next(true); + observer.complete(); + } else { + this.authService.getAuth().subscribe(auth => { + if (auth) { + this.authService.hasAnyRolesAsync('DataEntry', 'SuperDataEntry').subscribe(response => { + if (response) { + observer.next(true); + observer.complete(); + } else { + observer.next(this.router.parseUrl('/browse-substance')); + observer.complete(); + } + }); + } else { + const navigationExtras: NavigationExtras = { + queryParams: { + path: state.url + } + }; + observer.next(this.router.createUrlTree(['/login'], navigationExtras)); + observer.complete(); + } + }); + } }); } } diff --git a/src/app/core/substance/substance.service.ts b/src/app/core/substance/substance.service.ts index a83bf065b..acd62eada 100644 --- a/src/app/core/substance/substance.service.ts +++ b/src/app/core/substance/substance.service.ts @@ -1,6 +1,6 @@ import { Injectable } from '@angular/core'; import { HttpClient, HttpParams, HttpClientJsonpModule, HttpParameterCodec } from '@angular/common/http'; -import { BehaviorSubject, interval, Observable, Observer, Subject } from 'rxjs'; +import { BehaviorSubject, concatMap, filter, interval, Observable, Observer, Subject, throwError } from 'rxjs'; import { ConfigService } from '../config/config.service'; import { BaseHttpService } from '../base/base-http.service'; import { @@ -27,6 +27,7 @@ import {HierarchyNode} from '@gsrs-core/substances-browse/substance-hierarchy/hi import { SubstanceDependenciesImageNode } from '@gsrs-core/substance-details/substance-dependencies-image/substance-dependencies-image.model'; import { stringify } from 'querystring'; +import { AuthService } from "@gsrs-core/auth"; class CustomEncoder implements HttpParameterCodec { encodeKey(key: string): string { return encodeURIComponent(key); @@ -58,6 +59,7 @@ export class SubstanceService extends BaseHttpService { tempObject: any; constructor( public http: HttpClient, + private authService: AuthService, public configService: ConfigService, private sanitizer: DomSanitizer, private utilsService: UtilsService, @@ -752,8 +754,16 @@ export class SubstanceService extends BaseHttpService { } + // Helper function to create an Observable that emits when the popup login window closes + waitForPopupToClose(popupWindow) { + return interval(1000).pipe( + takeWhile(() => !popupWindow.closed, true), + filter(() => popupWindow.closed) + ); + } + + saveSubstance(substance: SubstanceDetail, type?: string): Observable { - const url = `${this.apiBaseUrl}substances?view=internal`; let method = substance.uuid ? 'PUT' : 'POST'; if (type && type === 'import') { method = 'POST'; @@ -761,7 +771,43 @@ export class SubstanceService extends BaseHttpService { const options = { body: substance }; - return this.http.request(method, url, options); + + if (!this.configService.configData.isPfdaVersion) { + const url = `${this.apiBaseUrl}substances?view=internal`; + return this.http.request(method, url, options); + } else { + return this.authService.getAuth().pipe( + concatMap(auth => { + if (auth) { + // If authenticated, make the HTTP request + const url = `${this.pfdaApiBaseUrl}substances?view=internal`; + return this.http.request(method, url, options); + } else { + // If not authenticated, open the login window and wait for it to close + const height = 700; + const width = 700; + const left = (screen.width / 2) - (width / 2); + const top = (screen.height / 2) - (height / 2); + const loginWindow = window.open( + '/login?user_return_to=%2Fgsrs-auth%2Fclose-login-window', + 'pFda Login', + `height=${height},width=${width},top=${top},left=${left}` + ); + + // Use an observable to wait for the popup window to close + return this.waitForPopupToClose(loginWindow).pipe( + concatMap(() => { + // Retry saving the substance after the window closes + return this.saveSubstance(substance, type); + }) + ); + } + }), + catchError(error => { + return throwError(() => new Error('Failed to save substance.')); + }) + ); + } } validateSubstance(substance: SubstanceDetail, stagingID?: string): Observable { @@ -1015,7 +1061,7 @@ export class SubstanceService extends BaseHttpService { public GetStagedRecord(id:string) { let url = `${(this.configService.configData && this.configService.configData.apiBaseUrl) || '/' }api/v1/substances/stagingArea/${id}`; - + return this.http.get< any >(`${url}`); } diff --git a/src/environments/environment.model.ts b/src/environments/environment.model.ts index 82c5e56d2..9debe8d9a 100644 --- a/src/environments/environment.model.ts +++ b/src/environments/environment.model.ts @@ -1,5 +1,6 @@ export interface Environment { apiBaseUrl: string; + pfdaApiBaseUrl?: string | undefined; configFileLocation?: string; baseHref: string; clasicBaseHref: string; From a78b7bc89721698f58eb1ac002ec63b9eae97cda Mon Sep 17 00:00:00 2001 From: Petr Barta Date: Tue, 22 Oct 2024 15:40:36 +0200 Subject: [PATCH 005/408] Request CSRF token before every POST request --- src/app/core/auth/csrf-token.interceptor.ts | 37 +++++++++++---------- 1 file changed, 19 insertions(+), 18 deletions(-) diff --git a/src/app/core/auth/csrf-token.interceptor.ts b/src/app/core/auth/csrf-token.interceptor.ts index 597f28061..d7eb94911 100644 --- a/src/app/core/auth/csrf-token.interceptor.ts +++ b/src/app/core/auth/csrf-token.interceptor.ts @@ -1,35 +1,36 @@ import { Injectable } from '@angular/core'; -import { HttpInterceptor, HttpRequest, HttpHandler, HttpEvent } from '@angular/common/http'; -import { Observable } from 'rxjs'; +import {HttpInterceptor, HttpRequest, HttpHandler, HttpEvent, HttpClient} from '@angular/common/http'; +import {from, Observable, switchMap} from 'rxjs'; +import {ConfigService} from "@gsrs-core/config"; @Injectable() export class CsrfTokenInterceptor implements HttpInterceptor { - - constructor() {} + constructor(private http: HttpClient, private configService: ConfigService) {} intercept(request: HttpRequest, next: HttpHandler): Observable> { // CSRF token for GET and HEAD is not needed - if (['GET', 'HEAD'].includes(request.method)) { + if (['GET', 'HEAD'].includes(request.method) || !(this.configService.configData?.isPfdaVersion)) { return next.handle(request); } - // Parse CSRF token from HTML meta tag - const metaTag: HTMLMetaElement | null = document.querySelector('meta[name=csrf-token]'); - let csrfToken = metaTag?.content; - if (csrfToken === undefined) { - csrfToken = 'CSRF-TOKEN-NOT-PARSED'; - } + return from(this.fetchCsrfToken()).pipe( + switchMap((token: string) => { + const modifiedRequest = this.addCsrfToken(request, token); + return next.handle(modifiedRequest); + }) + ); + } - // Clone the request and add the CSRF token to the headers - const modifiedRequest = request.clone({ + private fetchCsrfToken(): Promise { + return this.http.get(`${this.configService.configData.apiBaseUrl}csrf-token`, { responseType: 'text' }).toPromise(); + } + + private addCsrfToken(request: HttpRequest, token: string): HttpRequest { + return request.clone({ setHeaders: { - // eslint-disable-next-line @typescript-eslint/naming-convention - 'X-CSRF-Token': csrfToken + 'X-CSRF-Token': token } }); - - // Pass the modified request to the next handler - return next.handle(modifiedRequest); } } From f5d7639b12ef796bec0da3bfaac2430309fff2c9 Mon Sep 17 00:00:00 2001 From: Petr Barta Date: Wed, 23 Oct 2024 14:43:46 +0200 Subject: [PATCH 006/408] CSRF token uri --- src/app/core/auth/csrf-token.interceptor.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/core/auth/csrf-token.interceptor.ts b/src/app/core/auth/csrf-token.interceptor.ts index d7eb94911..35a5f8de0 100644 --- a/src/app/core/auth/csrf-token.interceptor.ts +++ b/src/app/core/auth/csrf-token.interceptor.ts @@ -23,7 +23,7 @@ export class CsrfTokenInterceptor implements HttpInterceptor { } private fetchCsrfToken(): Promise { - return this.http.get(`${this.configService.configData.apiBaseUrl}csrf-token`, { responseType: 'text' }).toPromise(); + return this.http.get(`/csrf-token`, { responseType: 'text' }).toPromise(); } private addCsrfToken(request: HttpRequest, token: string): HttpRequest { From bc656cc386e655d2ef42c201e64c933e150f2a3b Mon Sep 17 00:00:00 2001 From: Petr Barta Date: Wed, 20 Nov 2024 08:16:12 -0600 Subject: [PATCH 007/408] Login, logout fix; improvements --- src/app/core/auth/auth.service.ts | 11 ++- src/app/core/auth/csrf-token.interceptor.ts | 6 +- .../pfda-toolbar/pfda-toolbar.component.html | 8 +-- .../pfda-toolbar/pfda-toolbar.component.scss | 1 + .../pfda-toolbar/pfda-toolbar.component.ts | 9 +++ .../substance-form.component.ts | 5 +- src/app/core/substance/substance.service.ts | 72 +++++++++---------- 7 files changed, 66 insertions(+), 46 deletions(-) diff --git a/src/app/core/auth/auth.service.ts b/src/app/core/auth/auth.service.ts index 317fb295e..62e483afc 100644 --- a/src/app/core/auth/auth.service.ts +++ b/src/app/core/auth/auth.service.ts @@ -161,8 +161,15 @@ export class AuthService { document.cookie = name + '=;expires=Thu, 01 Jan 1970 00:00:00 GMT'; } } - const url = `${this.configService.configData.apiBaseUrl}logout`; - this.http.get(url).subscribe(() => { + let url = `${this.configService.configData.apiBaseUrl}logout`; + let method = 'GET'; + + if (this.configService.configData.isPfdaVersion) { + url = '/logout'; + method = 'DELETE'; + } + + this.http.request(method, url).subscribe(() => { this._auth = null; this._authUpdate.next(null); }, error => { diff --git a/src/app/core/auth/csrf-token.interceptor.ts b/src/app/core/auth/csrf-token.interceptor.ts index 35a5f8de0..854fce46f 100644 --- a/src/app/core/auth/csrf-token.interceptor.ts +++ b/src/app/core/auth/csrf-token.interceptor.ts @@ -9,8 +9,10 @@ export class CsrfTokenInterceptor implements HttpInterceptor { intercept(request: HttpRequest, next: HttpHandler): Observable> { - // CSRF token for GET and HEAD is not needed - if (['GET', 'HEAD'].includes(request.method) || !(this.configService.configData?.isPfdaVersion)) { + // CSRF token request needed in pFDA version only, for POST and DELETE requests made on /gsrs-auth/* and /logout endpoints + if (['GET', 'HEAD'].includes(request.method) + || (!request.url.toLowerCase().includes('/gsrs-auth/') && !request.url.toLowerCase().includes('/logout')) + || !(this.configService.configData?.isPfdaVersion)) { return next.handle(request); } diff --git a/src/app/core/base/pfda-toolbar/pfda-toolbar.component.html b/src/app/core/base/pfda-toolbar/pfda-toolbar.component.html index 3e2bd168b..7f867f2b4 100644 --- a/src/app/core/base/pfda-toolbar/pfda-toolbar.component.html +++ b/src/app/core/base/pfda-toolbar/pfda-toolbar.component.html @@ -61,12 +61,12 @@
- +
Login
-
+ diff --git a/src/app/core/base/pfda-toolbar/pfda-toolbar.component.scss b/src/app/core/base/pfda-toolbar/pfda-toolbar.component.scss index 8a863ed32..d5e1fc63f 100644 --- a/src/app/core/base/pfda-toolbar/pfda-toolbar.component.scss +++ b/src/app/core/base/pfda-toolbar/pfda-toolbar.component.scss @@ -47,6 +47,7 @@ $screenMedium: 1045px; align-items: center; justify-content: center; padding: 10px 6px; + cursor: pointer; &:hover { color: $pfda-navbar-item-hover; diff --git a/src/app/core/base/pfda-toolbar/pfda-toolbar.component.ts b/src/app/core/base/pfda-toolbar/pfda-toolbar.component.ts index f05646868..42b52f1fe 100644 --- a/src/app/core/base/pfda-toolbar/pfda-toolbar.component.ts +++ b/src/app/core/base/pfda-toolbar/pfda-toolbar.component.ts @@ -87,4 +87,13 @@ export class PfdaToolbarComponent implements OnInit { removeZindex(): void { this.overlayContainer.style.zIndex = null; } + + login(): void { + const locationEncoded = encodeURIComponent(`${window.location.pathname}${window.location.search}`); + window.location.assign(`${this.pfdaBaseUrl}login?user_return_to=${locationEncoded}`); + } + + logout(): void { + this.authService.logout(); + } } diff --git a/src/app/core/substance-form/substance-form.component.ts b/src/app/core/substance-form/substance-form.component.ts index c7c118989..0a15a2b80 100644 --- a/src/app/core/substance-form/substance-form.component.ts +++ b/src/app/core/substance-form/substance-form.component.ts @@ -1056,6 +1056,7 @@ export class SubstanceFormComponent implements OnInit, AfterViewInit, OnDestroy } this.openSuccessDialog({ type: 'submit', fileUrl: response.fileUrl }); }, (error: SubstanceFormResults) => { + console.log('error: ', error); this.showSubmissionMessages = true; this.loadingService.setLoading(false); this.isLoading = false; @@ -1099,7 +1100,9 @@ export class SubstanceFormComponent implements OnInit, AfterViewInit, OnDestroy messageType: 'ERROR', message: 'Unknown Server Error' }; - if (error && error.error && error.error.message) { + if (error && error.type === 'AUTH') { + message.message = `Authentication Error: ${error.message}`; + } else if (error && error.error && error.error.message) { message.message = 'Server Error ' + (error.status + ': ' || ': ') + error.error.message; } else if (error && error.error && (typeof error.error) === 'string') { message.message = 'Server Error ' + (error.status + ': ' || '') + error.error; diff --git a/src/app/core/substance/substance.service.ts b/src/app/core/substance/substance.service.ts index acd62eada..a5b0f1a9d 100644 --- a/src/app/core/substance/substance.service.ts +++ b/src/app/core/substance/substance.service.ts @@ -764,52 +764,50 @@ export class SubstanceService extends BaseHttpService { saveSubstance(substance: SubstanceDetail, type?: string): Observable { - let method = substance.uuid ? 'PUT' : 'POST'; - if (type && type === 'import') { - method = 'POST'; - } - const options = { - body: substance - }; + const method = type === 'import' || !substance.uuid ? 'POST' : 'PUT'; + const options = { body: substance }; + + const url = this.configService.configData.isPfdaVersion + ? `${this.pfdaApiBaseUrl}substances?view=internal` + : `${this.apiBaseUrl}substances?view=internal`; if (!this.configService.configData.isPfdaVersion) { - const url = `${this.apiBaseUrl}substances?view=internal`; return this.http.request(method, url, options); } else { return this.authService.getAuth().pipe( - concatMap(auth => { - if (auth) { - // If authenticated, make the HTTP request - const url = `${this.pfdaApiBaseUrl}substances?view=internal`; - return this.http.request(method, url, options); - } else { - // If not authenticated, open the login window and wait for it to close - const height = 700; - const width = 700; - const left = (screen.width / 2) - (width / 2); - const top = (screen.height / 2) - (height / 2); - const loginWindow = window.open( - '/login?user_return_to=%2Fgsrs-auth%2Fclose-login-window', - 'pFda Login', - `height=${height},width=${width},top=${top},left=${left}` - ); - - // Use an observable to wait for the popup window to close - return this.waitForPopupToClose(loginWindow).pipe( - concatMap(() => { - // Retry saving the substance after the window closes - return this.saveSubstance(substance, type); - }) - ); - } - }), - catchError(error => { - return throwError(() => new Error('Failed to save substance.')); - }) + concatMap(auth => auth + ? this.http.request(method, url, options) + : this.handlePfdaLoginAndRetry(method, url, options) + ) ); } } + private handlePfdaLoginAndRetry(method: string, url: string, options: any): Observable { + const height = 700; + const width = 700; + const left = (screen.width / 2) - (width / 2); + const top = (screen.height / 2) - (height / 2); + const loginWindow = window.open( + '/login?user_return_to=%2Fgsrs-auth%2Fclose-login-window', + 'pFDA Login', + `height=${height},width=${width},top=${top},left=${left}` + ); + + return this.waitForPopupToClose(loginWindow).pipe( + concatMap(() => + this.authService.getAuth().pipe( + concatMap(authAfterLogin => + authAfterLogin + ? this.http.request(method, url, options) + : throwError(() => ({ type: 'AUTH', message: 'Authentication failed' })) + ) + ) + ) + ); + } + + validateSubstance(substance: SubstanceDetail, stagingID?: string): Observable { let url = `${this.configService.configData.apiBaseUrl}api/v1/substances/@validate`; if (stagingID) { From 65e4f6b367bddad0f6636b4d8f12a9f9efcda2c6 Mon Sep 17 00:00:00 2001 From: Petr Barta Date: Tue, 26 Nov 2024 14:40:21 +0100 Subject: [PATCH 008/408] PFDA auth update --- src/app/core/auth/auth.service.ts | 40 ++++++++++++++- .../session-expiration-dialog.component.html | 1 + .../session-expiration-dialog.component.ts | 12 ++++- .../pfda-toolbar/pfda-toolbar.component.ts | 10 ++-- src/app/core/substance/substance.service.ts | 50 +++++-------------- 5 files changed, 69 insertions(+), 44 deletions(-) diff --git a/src/app/core/auth/auth.service.ts b/src/app/core/auth/auth.service.ts index 62e483afc..3ad9860c8 100644 --- a/src/app/core/auth/auth.service.ts +++ b/src/app/core/auth/auth.service.ts @@ -1,8 +1,8 @@ import { Injectable, PLATFORM_ID, Inject } from '@angular/core'; import { ConfigService } from '../config/config.service'; import { Auth, Role, UserGroup } from './auth.model'; -import { Observable, Subject, of } from 'rxjs'; -import { map, take, catchError } from 'rxjs/operators'; +import { interval, Observable, Subject, of } from 'rxjs'; +import { catchError, concatMap, filter, map, take, takeWhile } from 'rxjs/operators'; import { HttpClient, HttpParams } from '@angular/common/http'; import { isPlatformBrowser } from '@angular/common'; import { UserDownload, AllUserDownloads } from '@gsrs-core/auth/user-downloads/download.model'; @@ -96,6 +96,36 @@ export class AuthService { ); } + // Helper function to create an Observable that emits when the popup login window closes + private waitForPopupToClose(popupWindow: Window): Observable { + return interval(1000).pipe( + takeWhile(() => !popupWindow.closed, true), + filter(() => popupWindow.closed) + ); + } + + // Method to handle pFDA login (using popup window) and return success/unsuccess flag + pfdaLogin(): Observable { + const height = 700; + const width = 700; + const left = (screen.width / 2) - (width / 2); + const top = (screen.height / 2) - (height / 2); + const loginWindow = window.open( + '/login?user_return_to=%2Fgsrs-auth%2Fclose-login-window', + 'pFDA Login', + `height=${height},width=${width},top=${top},left=${left}` + ); + + return this.waitForPopupToClose(loginWindow).pipe( + concatMap(() => + this.getAuth().pipe( + map(authAfterLogin => !!authAfterLogin), // Convert to boolean (true = success) + catchError(() => of(false)) // Return false if there's an error + ) + ) + ); + } + getAuth(): Observable { return new Observable(observer => { @@ -143,6 +173,10 @@ export class AuthService { }); } + private deleteCookie(name: string) { + document.cookie = `${name}=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/`; + } + logout(): void { // if ( // !this.configService.configData @@ -172,9 +206,11 @@ export class AuthService { this.http.request(method, url).subscribe(() => { this._auth = null; this._authUpdate.next(null); + this.deleteCookie('sessionExpiredAt'); }, error => { this._auth = null; this._authUpdate.next(null); + this.deleteCookie('sessionExpiredAt'); }); } diff --git a/src/app/core/auth/session-expiration/session-expiration-dialog/session-expiration-dialog.component.html b/src/app/core/auth/session-expiration/session-expiration-dialog/session-expiration-dialog.component.html index c35a6869c..2903ea40f 100644 --- a/src/app/core/auth/session-expiration/session-expiration-dialog/session-expiration-dialog.component.html +++ b/src/app/core/auth/session-expiration/session-expiration-dialog/session-expiration-dialog.component.html @@ -6,6 +6,7 @@

+
diff --git a/src/app/core/auth/session-expiration/session-expiration-dialog/session-expiration-dialog.component.ts b/src/app/core/auth/session-expiration/session-expiration-dialog/session-expiration-dialog.component.ts index da96f8478..671946484 100644 --- a/src/app/core/auth/session-expiration/session-expiration-dialog/session-expiration-dialog.component.ts +++ b/src/app/core/auth/session-expiration/session-expiration-dialog/session-expiration-dialog.component.ts @@ -3,6 +3,7 @@ import { Router } from '@angular/router'; import { HttpClient } from '@angular/common/http'; import { SessionExpirationWarning } from '@gsrs-core/config'; import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog'; +import { AuthService } from '@gsrs-core/auth'; @Component({ selector: 'app-session-expiration-dialog', @@ -22,7 +23,8 @@ export class SessionExpirationDialogComponent implements OnInit { @Inject(MAT_DIALOG_DATA) public data: any, // N.B. injected services has to come after data private router: Router, - private http: HttpClient + private http: HttpClient, + private authService: AuthService ) { this.sessionExpirationWarning = data.sessionExpirationWarning; this.sessionExpiringAt = data.sessionExpiringAt; @@ -75,4 +77,12 @@ export class SessionExpirationDialogComponent implements OnInit { login() { window.location.assign('/login'); } + + proceedAsGuest() { + clearInterval(this.updateDialogInterval); + if (this.timeRemainingSeconds > 0) { + this.authService.logout(); + } + this.closeDialog(); + } } diff --git a/src/app/core/base/pfda-toolbar/pfda-toolbar.component.ts b/src/app/core/base/pfda-toolbar/pfda-toolbar.component.ts index 42b52f1fe..7e922b555 100644 --- a/src/app/core/base/pfda-toolbar/pfda-toolbar.component.ts +++ b/src/app/core/base/pfda-toolbar/pfda-toolbar.component.ts @@ -5,7 +5,7 @@ import { OverlayContainer } from '@angular/cdk/overlay'; import { AuthService } from '../../auth/auth.service'; import { SubstanceTextSearchService } from '@gsrs-core/substance-text-search/substance-text-search.service'; import { Auth } from '../../auth/auth.model'; -import { Subscription } from 'rxjs'; +import { concatMap, Subscription } from 'rxjs'; import { NavItem } from '@gsrs-core/config'; @Component({ @@ -40,7 +40,7 @@ export class PfdaToolbarComponent implements OnInit { ngOnInit() { this.pfdaBaseUrl = this.configService.configData.pfdaBaseUrl || '/'; - const baseHref = this.configService.environment.baseHref || '/' + const baseHref = this.configService.environment.baseHref || '/ginas/app/beta/'; this.logoSrcPath = `${baseHref}assets/images/pfda/pfda-logo.png`; this.homeIconPath = `${baseHref}assets/images/pfda/home.svg`; @@ -89,8 +89,10 @@ export class PfdaToolbarComponent implements OnInit { } login(): void { - const locationEncoded = encodeURIComponent(`${window.location.pathname}${window.location.search}`); - window.location.assign(`${this.pfdaBaseUrl}login?user_return_to=${locationEncoded}`); + this.authService.pfdaLogin().pipe( + concatMap(success => { + return this.authService.getAuth(); + })).subscribe(); } logout(): void { diff --git a/src/app/core/substance/substance.service.ts b/src/app/core/substance/substance.service.ts index a5b0f1a9d..03c30e61a 100644 --- a/src/app/core/substance/substance.service.ts +++ b/src/app/core/substance/substance.service.ts @@ -754,15 +754,6 @@ export class SubstanceService extends BaseHttpService { } - // Helper function to create an Observable that emits when the popup login window closes - waitForPopupToClose(popupWindow) { - return interval(1000).pipe( - takeWhile(() => !popupWindow.closed, true), - filter(() => popupWindow.closed) - ); - } - - saveSubstance(substance: SubstanceDetail, type?: string): Observable { const method = type === 'import' || !substance.uuid ? 'POST' : 'PUT'; const options = { body: substance }; @@ -775,39 +766,24 @@ export class SubstanceService extends BaseHttpService { return this.http.request(method, url, options); } else { return this.authService.getAuth().pipe( - concatMap(auth => auth - ? this.http.request(method, url, options) - : this.handlePfdaLoginAndRetry(method, url, options) + concatMap(auth => + auth + ? this.http.request(method, url, options) + : this.authService.pfdaLogin().pipe( + concatMap(success => + success + ? this.http.request(method, url, options) + : throwError(() => ({ + type: 'AUTH', + message: 'Authentication failed', + })) + ) + ) ) ); } } - private handlePfdaLoginAndRetry(method: string, url: string, options: any): Observable { - const height = 700; - const width = 700; - const left = (screen.width / 2) - (width / 2); - const top = (screen.height / 2) - (height / 2); - const loginWindow = window.open( - '/login?user_return_to=%2Fgsrs-auth%2Fclose-login-window', - 'pFDA Login', - `height=${height},width=${width},top=${top},left=${left}` - ); - - return this.waitForPopupToClose(loginWindow).pipe( - concatMap(() => - this.authService.getAuth().pipe( - concatMap(authAfterLogin => - authAfterLogin - ? this.http.request(method, url, options) - : throwError(() => ({ type: 'AUTH', message: 'Authentication failed' })) - ) - ) - ) - ); - } - - validateSubstance(substance: SubstanceDetail, stagingID?: string): Observable { let url = `${this.configService.configData.apiBaseUrl}api/v1/substances/@validate`; if (stagingID) { From e233a8d2063eb2bc6139b1376ffeb0afce0c7baf Mon Sep 17 00:00:00 2001 From: Petr Barta Date: Thu, 28 Nov 2024 15:11:28 +0100 Subject: [PATCH 009/408] Public GSRS --- src/app/core/auth/auth.service.ts | 51 +++---------------- src/app/core/auth/csrf-token.interceptor.ts | 9 ++-- .../session-expiration-dialog.component.ts | 19 +++++-- .../session-expiration.component.ts | 6 +-- src/app/core/base/base-http.service.ts | 4 -- src/app/core/config/config.model.ts | 1 - src/app/core/config/config.service.ts | 3 -- .../substance-form.component.html | 3 +- .../substance-form.component.ts | 16 +++++- src/app/core/substance/substance.service.ts | 5 +- src/environments/environment.model.ts | 1 - 11 files changed, 44 insertions(+), 74 deletions(-) diff --git a/src/app/core/auth/auth.service.ts b/src/app/core/auth/auth.service.ts index 3ad9860c8..59b86ebb1 100644 --- a/src/app/core/auth/auth.service.ts +++ b/src/app/core/auth/auth.service.ts @@ -111,7 +111,7 @@ export class AuthService { const left = (screen.width / 2) - (width / 2); const top = (screen.height / 2) - (height / 2); const loginWindow = window.open( - '/login?user_return_to=%2Fgsrs-auth%2Fclose-login-window', + '/login?user_return_to=%2Fginas%2Fclose-pfda-login-window', 'pFDA Login', `height=${height},width=${width},top=${top},left=${left}` ); @@ -346,55 +346,16 @@ export class AuthService { private fetchAuth(): Observable { return new Observable(observer => { this.configService.afterLoad().then(cd => { - const isPfdaVersion = this.configService.configData.isPfdaVersion === true; - const url = isPfdaVersion ? '/api/user' : - `${(this.configService.configData && this.configService.configData.apiBaseUrl) || '/'}api/v1/whoami`; + const url = `${(this.configService.configData && this.configService.configData.apiBaseUrl) || '/'}api/v1/`; if (this.configService.configData && this.configService.configData.dummyWhoami) { observer.next(this.configService.configData.dummyWhoami); } else { - this.http.get(url) + this.http.get(`${url}whoami`) .subscribe( auth => { - if (isPfdaVersion) { - // @ts-ignore - const dxuser = auth.user.dxuser; - const pfdaAuth: Auth = { - id: 0, - version: 0, - created: 0, - modified: 0, - deprecated: false, - user: { - id: 0, - version: 0, - created: 0, - modified: 0, - deprecated: false, - username: dxuser, - email: auth.user.email, - admin: auth.user.admin - }, - active: true, - systemAuth: false, - key: 'unused', - identifier: dxuser, - groups: [], - roles: [ - "Query", - "Updater", - "SuperUpdate", - "DataEntry", - "SuperDataEntry" - ], - computedToken: 'unused', - tokenTimeToExpireMS: 9999999999999, - roleQueryOnly: false, - permissions: [] - } - observer.next(pfdaAuth); - } else { - observer.next(auth); - } + // console.log("Authorized as"); + // console.log(auth); + observer.next(auth); }, err => { console.log("Authorized error"); diff --git a/src/app/core/auth/csrf-token.interceptor.ts b/src/app/core/auth/csrf-token.interceptor.ts index 854fce46f..412d3b5c1 100644 --- a/src/app/core/auth/csrf-token.interceptor.ts +++ b/src/app/core/auth/csrf-token.interceptor.ts @@ -1,7 +1,7 @@ import { Injectable } from '@angular/core'; -import {HttpInterceptor, HttpRequest, HttpHandler, HttpEvent, HttpClient} from '@angular/common/http'; -import {from, Observable, switchMap} from 'rxjs'; -import {ConfigService} from "@gsrs-core/config"; +import { HttpInterceptor, HttpRequest, HttpHandler, HttpEvent, HttpClient } from '@angular/common/http'; +import { from, Observable, switchMap } from 'rxjs'; +import { ConfigService } from "@gsrs-core/config"; @Injectable() export class CsrfTokenInterceptor implements HttpInterceptor { @@ -9,9 +9,8 @@ export class CsrfTokenInterceptor implements HttpInterceptor { intercept(request: HttpRequest, next: HttpHandler): Observable> { - // CSRF token request needed in pFDA version only, for POST and DELETE requests made on /gsrs-auth/* and /logout endpoints + // CSRF token request needed in pFDA version only if (['GET', 'HEAD'].includes(request.method) - || (!request.url.toLowerCase().includes('/gsrs-auth/') && !request.url.toLowerCase().includes('/logout')) || !(this.configService.configData?.isPfdaVersion)) { return next.handle(request); } diff --git a/src/app/core/auth/session-expiration/session-expiration-dialog/session-expiration-dialog.component.ts b/src/app/core/auth/session-expiration/session-expiration-dialog/session-expiration-dialog.component.ts index 671946484..051f1725b 100644 --- a/src/app/core/auth/session-expiration/session-expiration-dialog/session-expiration-dialog.component.ts +++ b/src/app/core/auth/session-expiration/session-expiration-dialog/session-expiration-dialog.component.ts @@ -1,9 +1,10 @@ import { Component, OnInit, Inject } from '@angular/core'; import { Router } from '@angular/router'; import { HttpClient } from '@angular/common/http'; -import { SessionExpirationWarning } from '@gsrs-core/config'; +import {ConfigService, SessionExpirationWarning} from '@gsrs-core/config'; import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog'; import { AuthService } from '@gsrs-core/auth'; +import {concatMap} from "rxjs"; @Component({ selector: 'app-session-expiration-dialog', @@ -24,7 +25,8 @@ export class SessionExpirationDialogComponent implements OnInit { // N.B. injected services has to come after data private router: Router, private http: HttpClient, - private authService: AuthService + private authService: AuthService, + public configService: ConfigService ) { this.sessionExpirationWarning = data.sessionExpirationWarning; this.sessionExpiringAt = data.sessionExpiringAt; @@ -75,7 +77,18 @@ export class SessionExpirationDialogComponent implements OnInit { } login() { - window.location.assign('/login'); + if (this.configService.configData.isPfdaVersion) { + this.authService.pfdaLogin().pipe( + concatMap(success => { + console.log('success: ', success); + if (success) { + this.closeDialog(); + return this.authService.getAuth(); + } + })).subscribe(); + } else { + window.location.assign('/login'); + } } proceedAsGuest() { diff --git a/src/app/core/auth/session-expiration/session-expiration.component.ts b/src/app/core/auth/session-expiration/session-expiration.component.ts index 1f903f2f1..16f4b7967 100644 --- a/src/app/core/auth/session-expiration/session-expiration.component.ts +++ b/src/app/core/auth/session-expiration/session-expiration.component.ts @@ -84,11 +84,7 @@ export class SessionExpirationComponent implements OnInit { } refreshSession(): any { - if (this.configService.configData.isPfdaVersion) { - fetch(`${this.configService.configData.pfdaApiBaseUrl}user`) - } else { - fetch(`${this.baseHref || ''}api/v1/whoami?key=${this.utilsService.newUUID()}`) - } + fetch(`${this.baseHref || ''}api/v1/whoami?key=${this.utilsService.newUUID()}`); } startSessionTimeoutInterval() { diff --git a/src/app/core/base/base-http.service.ts b/src/app/core/base/base-http.service.ts index fd27e461e..c8a7db553 100644 --- a/src/app/core/base/base-http.service.ts +++ b/src/app/core/base/base-http.service.ts @@ -2,16 +2,12 @@ import { ConfigService } from '../config/config.service'; export abstract class BaseHttpService { public apiBaseUrl: string; - public pfdaApiBaseUrl: string = ''; public baseUrl: string; constructor( public configService: ConfigService ) { this.apiBaseUrl = `${(this.configService.configData && this.configService.configData.apiBaseUrl) || '/' }api/v1/`; - if (this.configService.configData.isPfdaVersion && this.configService.configData.pfdaApiBaseUrl) { - this.pfdaApiBaseUrl = this.configService.configData.pfdaApiBaseUrl; - } this.baseUrl = (this.configService.configData && this.configService.configData.apiBaseUrl) || '/'; } } diff --git a/src/app/core/config/config.model.ts b/src/app/core/config/config.model.ts index 821401668..f04db4df7 100644 --- a/src/app/core/config/config.model.ts +++ b/src/app/core/config/config.model.ts @@ -2,7 +2,6 @@ import { Auth } from "@gsrs-core/auth"; export interface Config { apiBaseUrl?: string; - pfdaApiBaseUrl?: string; gsrsHomeBaseUrl?: string; apiSSG4mBaseUrl?: string; apiUrlDomain?: string; diff --git a/src/app/core/config/config.service.ts b/src/app/core/config/config.service.ts index 43586ac7b..e2a78a6af 100644 --- a/src/app/core/config/config.service.ts +++ b/src/app/core/config/config.service.ts @@ -47,9 +47,6 @@ export class ConfigService { if (config.apiBaseUrl == null && environment.apiBaseUrl != null) { config.apiBaseUrl = environment.apiBaseUrl; } - if (config.pfdaApiBaseUrl == null && environment.pfdaApiBaseUrl != null) { - config.pfdaApiBaseUrl = environment.pfdaApiBaseUrl; - } if (config.apiBaseUrl.indexOf('//') > -1) { const parts = config.apiBaseUrl.split('/'); config.apiUrlDomain = `${parts[0]}//${parts[2]}`; diff --git a/src/app/core/substance-form/substance-form.component.html b/src/app/core/substance-form/substance-form.component.html index bec1f84eb..2d7f3b82e 100644 --- a/src/app/core/substance-form/substance-form.component.html +++ b/src/app/core/substance-form/substance-form.component.html @@ -212,8 +212,7 @@

{{ section.menuLabel }}

+ + + + + + + + + + + + + + + + + + + +
@@ -97,6 +117,15 @@
+
+
+
+ {{pauseStructureSearch}} +
+
+
+ {{asyncFinished}} +
diff --git a/src/app/core/substances-browse/substances-browse.component.ts b/src/app/core/substances-browse/substances-browse.component.ts index bed97c53c..806094534 100644 --- a/src/app/core/substances-browse/substances-browse.component.ts +++ b/src/app/core/substances-browse/substances-browse.component.ts @@ -219,7 +219,7 @@ export class SubstancesBrowseComponent implements OnInit, AfterViewInit, OnDestr }); }); - + this.title.setTitle('Browse Substances'); this.pageSize = 10; @@ -248,7 +248,7 @@ export class SubstancesBrowseComponent implements OnInit, AfterViewInit, OnDestr this.privateSearchSeqType = this.activatedRoute.snapshot.queryParams['seq_type'] || ''; this.smiles = this.activatedRoute.snapshot.queryParams['smiles'] || ''; // the sort order should be set to default (similarity) for structure searches, last edited for all others - this.order = this.activatedRoute.snapshot.queryParams['order'] || + this.order = this.activatedRoute.snapshot.queryParams['order'] || (this.privateStructureSearchTerm && this.privateStructureSearchTerm !== '' ? 'default':'$root_lastEdited'); this.view = this.activatedRoute.snapshot.queryParams['view'] || 'cards'; this.pageSize = parseInt(this.activatedRoute.snapshot.queryParams['pageSize'], null) || 10; @@ -516,7 +516,7 @@ export class SubstancesBrowseComponent implements OnInit, AfterViewInit, OnDestr id:'structure-dialog' }); this.overlayContainer.style.zIndex = '1002'; - + this.structureSearchDialog.afterClosed().subscribe(result => { this.overlayContainer.style.zIndex = null; this.loadingService.setLoading(false); @@ -525,7 +525,7 @@ export class SubstancesBrowseComponent implements OnInit, AfterViewInit, OnDestr }); this.structureDialogOpened = true; } - + } searchSubstances() { @@ -580,7 +580,7 @@ export class SubstancesBrowseComponent implements OnInit, AfterViewInit, OnDestr // this.pauseStructureSearch = true; iterations++; } - + this.privateBulkSearchStatusKey = pagingResponse.statusKey; this.isError = false; @@ -620,7 +620,7 @@ export class SubstancesBrowseComponent implements OnInit, AfterViewInit, OnDestr this.etag = pagingResponse.etag; if (pagingResponse.facets && pagingResponse.facets.length > 0) { this.rawFacets = pagingResponse.facets; - + } this.narrowSearchSuggestions = {}; this.matchTypes = []; @@ -760,7 +760,7 @@ searchTermOkforBeginsWithSearch(): boolean { maxHeight: '85%', width: '60%', - + data: { 'extension': extension } }); @@ -1287,21 +1287,21 @@ searchTermOkforBeginsWithSearch(): boolean { addToList(): void { let data = {view: 'add', etag: this.etag, lists: this.userLists}; - + const dialogRef = this.dialog.open(UserQueryListDialogComponent, { width: '800px', autoFocus: false, data: data - + }); this.overlayContainer.style.zIndex = '1002'; - + const dialogSubscription = dialogRef.afterClosed().pipe(take(1)).subscribe(response => { if (response) { this.overlayContainer.style.zIndex = null; } }); } - + } diff --git a/src/app/fda/config/config.json b/src/app/fda/config/config.json index a16732a6c..23c417119 100644 --- a/src/app/fda/config/config.json +++ b/src/app/fda/config/config.json @@ -10,6 +10,7 @@ "bannerMessage": null, "showNameStandardizeButton": true, "advancedSearchFacetDisplay": false, + "apiBaseUrl": "https://gsrs.ncats.nih.gov/ginas/app/", "approvalCodeName": "UNII", "primaryCode": "BDNUM", "useDataUrl": false, From 2e66cf3e26126945ef615902709fb57b7648be09 Mon Sep 17 00:00:00 2001 From: NikoAnderson Date: Mon, 23 Dec 2024 10:26:50 -0500 Subject: [PATCH 019/408] final version for testing --- .../substances-browse.component.html | 31 ------------------- .../substances-browse.component.scss | 5 ++- 2 files changed, 2 insertions(+), 34 deletions(-) diff --git a/src/app/core/substances-browse/substances-browse.component.html b/src/app/core/substances-browse/substances-browse.component.html index 25e36e5f8..aa6ead17c 100644 --- a/src/app/core/substances-browse/substances-browse.component.html +++ b/src/app/core/substances-browse/substances-browse.component.html @@ -75,26 +75,6 @@ Or start a new chemical registration using this Smiles
- - - - - - - - - - - - - - - - - - - -
@@ -117,15 +97,6 @@
-
-
-
- {{pauseStructureSearch}} -
-
-
- {{asyncFinished}} -
@@ -730,5 +701,3 @@

{{privateSearchType | titlecase }} Search is Processing...


- - diff --git a/src/app/core/substances-browse/substances-browse.component.scss b/src/app/core/substances-browse/substances-browse.component.scss index a342a6572..6011c967b 100644 --- a/src/app/core/substances-browse/substances-browse.component.scss +++ b/src/app/core/substances-browse/substances-browse.component.scss @@ -346,7 +346,7 @@ display: flex; font-family: Menlo,Monaco,Consolas,"Courier New",monospace; color: var(--pink-span-color); } - + .similarity-label { font-style: italic; } @@ -626,7 +626,7 @@ display: flex; ::ng-deep .mat-select-value { max-width: 100%; width: auto; - } + } } .page-label { @@ -774,4 +774,3 @@ margin-left: 20px; line-height: 28px; margin-left: 20px; } - From bb02e750337ea9a7df28ac2a37099fd142902d67 Mon Sep 17 00:00:00 2001 From: NikoAnderson Date: Mon, 23 Dec 2024 10:28:11 -0500 Subject: [PATCH 020/408] adding service --- src/app/core/substance/substance.service.ts | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/src/app/core/substance/substance.service.ts b/src/app/core/substance/substance.service.ts index 8c4bd867b..1cffdfaee 100644 --- a/src/app/core/substance/substance.service.ts +++ b/src/app/core/substance/substance.service.ts @@ -267,7 +267,6 @@ export class SubstanceService extends BaseHttpService { sync = true; } if (!sync && this.searchKeys[structureFacetsKey]) { - console.log('not sync'); url += `status(${this.searchKeys[structureFacetsKey]})/results`; params = params.appendFacetParams(facets, this.showDeprecated); if(querySearchTerm.length > 0) { @@ -285,12 +284,8 @@ export class SubstanceService extends BaseHttpService { if (order != null && order !== '') { params = params.append('order', order); } - console.log(url); - console.log(params); } else { - console.log(sync); - console.log(type); params = params.append('q', (searchTerm)); if (type) { params = params.append('type', type); @@ -315,7 +310,6 @@ export class SubstanceService extends BaseHttpService { } } url += 'substances/structureSearch'; - console.log(url); } const options = { @@ -324,10 +318,8 @@ export class SubstanceService extends BaseHttpService { this.http.get(url, options).subscribe( response => { - console.log(response); // call async if (response.results) { - console.log('call async'); const resultKey = response.key; this.searchKeys[structureFacetsKey] = resultKey; this.processAsyncSearchResults( @@ -342,7 +334,6 @@ export class SubstanceService extends BaseHttpService { skip ); } else { - console.log('complete'); observer.next(response); observer.complete(); } @@ -488,8 +479,6 @@ export class SubstanceService extends BaseHttpService { response => { // call async if (response.results) { - console.log('has results'); - console.log(response); const resultKey = response.key; this.searchKeys[bulkFacetsKey] = resultKey; this.processAsyncSearchResults( @@ -505,7 +494,6 @@ export class SubstanceService extends BaseHttpService { ); } else { // consider making API backend provide statusKey in JSON - console.log('not results)'); if(this.searchKeys && this.searchKeys[bulkFacetsKey]) { response.statusKey = this.searchKeys[bulkFacetsKey]; } From be0b95e736277fb07773fc95368d7e474d431b12 Mon Sep 17 00:00:00 2001 From: NikoAnderson Date: Mon, 23 Dec 2024 10:31:05 -0500 Subject: [PATCH 021/408] removing local changes --- src/app/fda/config/config.json | 1 - 1 file changed, 1 deletion(-) diff --git a/src/app/fda/config/config.json b/src/app/fda/config/config.json index 23c417119..a16732a6c 100644 --- a/src/app/fda/config/config.json +++ b/src/app/fda/config/config.json @@ -10,7 +10,6 @@ "bannerMessage": null, "showNameStandardizeButton": true, "advancedSearchFacetDisplay": false, - "apiBaseUrl": "https://gsrs.ncats.nih.gov/ginas/app/", "approvalCodeName": "UNII", "primaryCode": "BDNUM", "useDataUrl": false, From 1588625e3a38f896cf41c2968e34f1c44fee678a Mon Sep 17 00:00:00 2001 From: NikoAnderson Date: Tue, 21 Jan 2025 15:16:12 -0500 Subject: [PATCH 022/408] adding formulation autofill option for ssg1 --- .../constituents/substance-form-constituents-card.component.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/core/substance-form/constituents/substance-form-constituents-card.component.ts b/src/app/core/substance-form/constituents/substance-form-constituents-card.component.ts index a0a25052a..4b6b092e8 100644 --- a/src/app/core/substance-form/constituents/substance-form-constituents-card.component.ts +++ b/src/app/core/substance-form/constituents/substance-form-constituents-card.component.ts @@ -78,7 +78,7 @@ export class SubstanceFormConstituentsCardComponent extends SubstanceCardBaseFil this.formulationPercent = 0; this.components = 0; this.constituents.forEach(constituent => { - if(constituent && constituent.amount && constituent.amount.type === "WEIGHT PERCENT" + if(constituent && constituent.amount && constituent.amount.type === "WEIGHT PERCENT" && constituent.amount.units === "%" && constituent.amount.average) { this.formulationPercent = parseFloat(this.formulationPercent.toString()) + parseFloat(constituent.amount.average.toString()); this.components++; From fee2e109f7d589f92f8e20f964edda32429083e2 Mon Sep 17 00:00:00 2001 From: NikoAnderson Date: Thu, 23 Jan 2025 11:09:12 -0500 Subject: [PATCH 023/408] adding formulation percentage for G1ss, det.pagin --- .../substance-codes/substance-codes.component.html | 8 ++++---- .../substance-names/substance-names.component.html | 10 +++++----- .../substance-references.component.html | 2 +- .../substance-relationships.component.html | 4 ++-- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/app/core/substance-details/substance-codes/substance-codes.component.html b/src/app/core/substance-details/substance-codes/substance-codes.component.html index fae32366b..282837881 100644 --- a/src/app/core/substance-details/substance-codes/substance-codes.component.html +++ b/src/app/core/substance-details/substance-codes/substance-codes.component.html @@ -93,7 +93,7 @@ + [disabled] = "!code.comments && !code.codeText" >{{!code.comments && !code.codeText ? 'None' : 'View'}}

Code Comments

@@ -110,17 +110,17 @@

Code Comments

- +
- + References - diff --git a/src/app/core/substance-details/substance-names/substance-names.component.html b/src/app/core/substance-details/substance-names/substance-names.component.html index 2dc7a32c0..b8b5876b1 100644 --- a/src/app/core/substance-details/substance-names/substance-names.component.html +++ b/src/app/core/substance-details/substance-names/substance-names.component.html @@ -14,7 +14,7 @@ '>Both
- + {{showHideFilterText}} @@ -162,22 +162,22 @@ Details -

Details

- + -
- Naming organizations: + Naming organizations:
- {{org.nameOrg}}{{!last? ', ':''}} + {{org.nameOrg}}{{!last? ', ':''}}
diff --git a/src/app/core/substance-details/substance-references/substance-references.component.html b/src/app/core/substance-details/substance-references/substance-references.component.html index e33d554d8..9ec599324 100644 --- a/src/app/core/substance-details/substance-references/substance-references.component.html +++ b/src/app/core/substance-details/substance-references/substance-references.component.html @@ -97,7 +97,7 @@
Access + diff --git a/src/app/core/substance-details/substance-relationships/substance-relationships.component.html b/src/app/core/substance-details/substance-relationships/substance-relationships.component.html index d213b4b8b..3798f0ea2 100644 --- a/src/app/core/substance-details/substance-relationships/substance-relationships.component.html +++ b/src/app/core/substance-details/substance-relationships/substance-relationships.component.html @@ -33,12 +33,12 @@ Details -
{{filename? filename: 'no file chosen'}}
- - -
-
Or paste JSON here:
- -
-
- {{message}} -
+ +
+
+
{{filename? filename: 'no file chosen'}}
+ +
+ + +
+
Paste JSON here:
+ +
+
+ +
+
URL:
+ +
Note: The URL needs to be publicly accessible
+
+
+ +
+ {{message}} +

- -
\ No newline at end of file + + diff --git a/src/app/core/substance-edit-import-dialog/substance-edit-import-dialog.component.ts b/src/app/core/substance-edit-import-dialog/substance-edit-import-dialog.component.ts index dd28c539b..774d23b60 100644 --- a/src/app/core/substance-edit-import-dialog/substance-edit-import-dialog.component.ts +++ b/src/app/core/substance-edit-import-dialog/substance-edit-import-dialog.component.ts @@ -1,6 +1,8 @@ import { Component, OnInit, Inject } from '@angular/core'; -import { MatDialogRef, MAT_DIALOG_DATA, MatDialog } from '@angular/material/dialog'; +import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog'; import { Router } from '@angular/router'; +import { MatTabChangeEvent } from '@angular/material/tabs'; +import { ConfigService } from '@gsrs-core/config'; @Component({ selector: 'app-substance-edit-import-dialog', @@ -14,15 +16,21 @@ export class SubstanceEditImportDialogComponent implements OnInit { record: any; filename: string; pastedJSON: string; - uploaded = false; + pastedUrl: string; title = 'Substance Import'; entity = 'Substance'; + currentTab: number = 0; + urlImportEnabled: boolean = false; + constructor( private router: Router, + private configService: ConfigService, public dialogRef: MatDialogRef, @Inject(MAT_DIALOG_DATA) public data: any - ) { } + ) { + this.urlImportEnabled = this.configService.configData.isPfdaVersion; + } ngOnInit() { if (this.data) { @@ -59,36 +67,70 @@ export class SubstanceEditImportDialogComponent implements OnInit { } }; reader.readAsText(event.target.files[0]); - this.uploaded = true; } } - useFile() { - if (!this.uploaded && this.pastedJSON) { - const read = JSON.parse(this.pastedJSON); - if (!read['substanceClass']) { - this.message = 'Error: Invalid JSON format'; - this.loaded = false; + importSubstance() { + if (this.currentTab === 0) { + // Nothing + this.dialogRef.close(this.record); + } else if (this.currentTab === 1) { + const read = JSON.parse(this.pastedJSON); + if (!read['substanceClass']) { + this.message = 'Error: Invalid JSON format'; + this.loaded = false; + } else { + this.loaded = true; + this.record = this.pastedJSON; + this.message = ''; + this.dialogRef.close(this.record); + } + } else if (this.currentTab === 2) { + fetch(`/reverse-proxy?url=${this.pastedUrl}`).then(r => { + if (r.status !== 200) { + r.json().then(data => { + this.message = data.message ? data.message : 'Error while loading given URL'; + }).catch(_e => { + this.message = 'Error while loading given URL'; + }) } else { - this.loaded = true; - this.record = this.pastedJSON; - this.message = ''; + const json = r.text().then(data => { + try { + JSON.parse(data); + this.record = data; + this.dialogRef.close(this.record); + } catch (_e) { + this.message = 'Error: The URL does not point to a valid JSON file' + } + }); } + }).catch(e => { + this.message = `Error: ${e.message}`; + }) } - this.dialogRef.close(this.record); } - checkLoaded() { this.loaded = true; try { JSON.parse(this.pastedJSON); this.message = ''; - } catch (e) { - this.message = 'Error: Invalid JSON format in pasted string'; - this.loaded = false; + } catch (e) { + this.message = 'Error: Invalid JSON format in pasted string'; + this.loaded = false; + } + } + + checkUrl() { + try { + new URL(this.pastedUrl); + this.loaded = true; + this.message = ''; + } catch (_e) { + this.message = 'Invalid URL'; + this.loaded = false; + } } -} openInput(): void { @@ -104,4 +146,15 @@ export class SubstanceEditImportDialogComponent implements OnInit { return true; } + tabChanged(tabChangeEvent: MatTabChangeEvent) { + if (this.currentTab !== tabChangeEvent.index) { + this.currentTab = tabChangeEvent.index; + this.message = ''; + this.loaded = false; + this.record = ''; + this.pastedJSON = ''; + this.pastedUrl = ''; + this.filename = ''; + } + } } From 8ed0fb830ad3afc468712b96cffda80d66150ab0 Mon Sep 17 00:00:00 2001 From: Petr Barta Date: Mon, 24 Feb 2025 12:16:18 +0100 Subject: [PATCH 033/408] Success dialog after saving G4SSM in pfda --- .../model/substance-ssg4m.model.ts | 1 + .../substance-ssg4m-form.component.ts | 26 +++++++++++++++---- 2 files changed, 22 insertions(+), 5 deletions(-) diff --git a/src/app/core/substance-ssg4m/model/substance-ssg4m.model.ts b/src/app/core/substance-ssg4m/model/substance-ssg4m.model.ts index 5555d42fa..9a11ebdfd 100644 --- a/src/app/core/substance-ssg4m/model/substance-ssg4m.model.ts +++ b/src/app/core/substance-ssg4m/model/substance-ssg4m.model.ts @@ -17,6 +17,7 @@ export interface Ssg4mSyntheticPathway { printSbstncPrfrdNm?: string; sbmsnImage?: string; ssg4mSyntheticPathwayDetailsList?: Array; + fileUrl?: string; } export interface Ssg4mSyntheticPathwayDetail { diff --git a/src/app/core/substance-ssg4m/substance-ssg4m-form.component.ts b/src/app/core/substance-ssg4m/substance-ssg4m-form.component.ts index 070477b57..975f8ea2a 100644 --- a/src/app/core/substance-ssg4m/substance-ssg4m-form.component.ts +++ b/src/app/core/substance-ssg4m/substance-ssg4m-form.component.ts @@ -1027,7 +1027,7 @@ export class SubstanceSsg4ManufactureFormComponent implements OnInit, AfterViewI this.validationResult = false; // if Saved Successfully - if (response && response.synthPathwaySkey) { + if (response && (response.synthPathwaySkey || this.configService.configData.isPfdaVersion)) { if (response.synthPathwaySkey) { this.id = response.synthPathwaySkey.toString(); } @@ -1043,7 +1043,7 @@ export class SubstanceSsg4ManufactureFormComponent implements OnInit, AfterViewI this.saveDelayedMessage = ""; this.isCancelBtnClicked = false; // Only show successful dialog and refresh page, if user does not click on the cancel button. - this.openSuccessDialog(); + this.openSuccessDialog(undefined, this.configService.configData.isPfdaVersion ? response.fileUrl : null); } // Refresh the current page, this will not cause record locking issue /* this.router.routeReuseStrategy.shouldReuseRoute = () => false; @@ -1083,7 +1083,7 @@ export class SubstanceSsg4ManufactureFormComponent implements OnInit, AfterViewI setTimeout(tempCallback(s),3000); }; - + window['schemeUtil'].renderScheme(window['schemeUtil'].makeDisplayGraph(JSON.parse(ssgjs)), "#scheme-viz-view"); } @@ -1284,10 +1284,21 @@ export class SubstanceSsg4ManufactureFormComponent implements OnInit, AfterViewI return old; } - openSuccessDialog(type?: string): void { + openSuccessDialog(type?: string, fileUrl?: string): void { let data = { - isCoreSubstance: 'false' + isCoreSubstance: 'false', + type: null, + fileUrl: null }; + + if (this.configService.configData.isPfdaVersion) { + data = { + isCoreSubstance: 'true', + type: 'submit', + fileUrl: fileUrl + } + } + const dialogRef = this.dialog.open(SubmitSuccessDialogComponent, { data: data }); @@ -1323,6 +1334,11 @@ export class SubstanceSsg4ManufactureFormComponent implements OnInit, AfterViewI this.router.navigate(['/substances-ssg4m', this.id, 'edit']); }, 3000); */ + } else if (response === 'browse') { + this.router.navigate(['/browse-substance']); + } else if (response === 'viewInPfda') { + // View the submitted substance file in the user's precisionFDA home + window.location.assign(fileUrl); } }); this.subscriptions.push(dialogSubscription); From 0313387343b5a2eafe677efc8555d8020c6943a7 Mon Sep 17 00:00:00 2001 From: Petr Barta Date: Thu, 27 Feb 2025 11:21:50 +0100 Subject: [PATCH 034/408] Login required when trying to submit SSG4m in pfda --- .../substance-ssg4m-form.service.ts | 26 +++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/src/app/core/substance-ssg4m/substance-ssg4m-form.service.ts b/src/app/core/substance-ssg4m/substance-ssg4m-form.service.ts index 877df79bd..cad349ce9 100644 --- a/src/app/core/substance-ssg4m/substance-ssg4m-form.service.ts +++ b/src/app/core/substance-ssg4m/substance-ssg4m-form.service.ts @@ -7,10 +7,11 @@ import { Ssg4mSyntheticPathway, Ssg4mSyntheticPathwayDetail } from './model/subs import { SubstanceDetail } from '../substance/substance.model'; import { SubstanceName } from '../substance/substance.model'; import { SubstanceFormDefinition, SubunitSequence, ValidationResults, ValidationMessage } from '../substance-form/substance-form.model'; -import { Observable, Subject, ReplaySubject, Subscription } from 'rxjs'; +import { Observable, Subject, ReplaySubject, Subscription, concatMap, throwError } from 'rxjs'; import { SubstanceService } from '@gsrs-core/substance/substance.service'; import { UtilsService } from '@gsrs-core/utils/utils.service'; import { StructureService } from '@gsrs-core/structure'; +import { AuthService } from '@gsrs-core/auth'; @Injectable({ providedIn: 'root' @@ -55,6 +56,7 @@ export class SubstanceSsg4mService implements OnDestroy { public utilsService: UtilsService, private structureService: StructureService, public http: HttpClient, + private authService: AuthService, public configService: ConfigService ) { this.substanceEmitter = new ReplaySubject(); @@ -310,7 +312,27 @@ export class SubstanceSsg4mService implements OnDestroy { const options = { body: ssg4m }; - return this.http.request(method, url, options); + + if (!this.configService.configData.isPfdaVersion) { + return this.http.request(method, url, options); + } else { + return this.authService.getAuth().pipe( + concatMap(auth => + auth + ? this.http.request(method, url, options) + : this.authService.pfdaLogin().pipe( + concatMap(success => + success + ? this.http.request(method, url, options) + : throwError(() => ({ + type: 'AUTH', + message: 'Authentication failed', + })) + ) + ) + ) + ); + } } validateSsg4m(ssg4m: Ssg4mSyntheticPathway): Observable { From 1dcfce9c110898082964ffb6dcab15fb60ef2ef9 Mon Sep 17 00:00:00 2001 From: Petr Barta Date: Thu, 27 Mar 2025 01:12:11 -0600 Subject: [PATCH 035/408] Import G4SSM from PFDA --- src/app/core/substance-form/substance-form.component.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/app/core/substance-form/substance-form.component.ts b/src/app/core/substance-form/substance-form.component.ts index 86fcce163..af570dd9f 100644 --- a/src/app/core/substance-form/substance-form.component.ts +++ b/src/app/core/substance-form/substance-form.component.ts @@ -245,6 +245,12 @@ export class SubstanceFormComponent implements OnInit, AfterViewInit, OnDestroy // There are probably other components affected. There is an issue with subscriptions likely due to some OnInit not firing const read = JSON.parse(response); + + if (read.substanceClass === 'specifiedSubstanceG4m') { + this.router.navigateByUrl('/substances-ssg4m/register?action=import&header=' + true, { state: { record: response } }); + return; + } + if (this.id && read.uuid && this.id === read.uuid) { this.substanceFormService.importSubstance(read, 'update'); this.submissionMessage = null; From 5d387825d204d80179204208571f8875ed4d6176 Mon Sep 17 00:00:00 2001 From: Petr Barta Date: Wed, 23 Apr 2025 11:46:21 +0200 Subject: [PATCH 036/408] PFDA Support email address loaded from config file --- src/app/core/base/pfda-toolbar/pfda-toolbar.component.html | 2 +- src/app/core/base/pfda-toolbar/pfda-toolbar.component.ts | 2 ++ src/app/core/config/config.pfda.json | 2 +- src/app/fda/config/config.json | 2 +- 4 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/app/core/base/pfda-toolbar/pfda-toolbar.component.html b/src/app/core/base/pfda-toolbar/pfda-toolbar.component.html index 7f867f2b4..1d0177b47 100644 --- a/src/app/core/base/pfda-toolbar/pfda-toolbar.component.html +++ b/src/app/core/base/pfda-toolbar/pfda-toolbar.component.html @@ -46,7 +46,7 @@ (closed)="removeZindex()"> - +
Support
diff --git a/src/app/core/base/pfda-toolbar/pfda-toolbar.component.ts b/src/app/core/base/pfda-toolbar/pfda-toolbar.component.ts index 7e922b555..60d727f5d 100644 --- a/src/app/core/base/pfda-toolbar/pfda-toolbar.component.ts +++ b/src/app/core/base/pfda-toolbar/pfda-toolbar.component.ts @@ -15,6 +15,7 @@ import { NavItem } from '@gsrs-core/config'; }) export class PfdaToolbarComponent implements OnInit { pfdaBaseUrl: string; + supportEmail: string; logoSrcPath: string; homeIconPath: string; auth?: Auth; @@ -43,6 +44,7 @@ export class PfdaToolbarComponent implements OnInit { const baseHref = this.configService.environment.baseHref || '/ginas/app/beta/'; this.logoSrcPath = `${baseHref}assets/images/pfda/pfda-logo.png`; this.homeIconPath = `${baseHref}assets/images/pfda/home.svg`; + this.supportEmail = this.configService.configData.contactEmail || 'fda-srs@fda.hhs.gov'; this.overlayContainer = this.overlayContainerService.getContainerElement(); diff --git a/src/app/core/config/config.pfda.json b/src/app/core/config/config.pfda.json index 54955437b..c036b5f06 100644 --- a/src/app/core/config/config.pfda.json +++ b/src/app/core/config/config.pfda.json @@ -532,7 +532,7 @@ "root_codes_CAS", "root_codes_ECHA\\ \\(EC\/EINECS\\)" ], - "contactEmail": "precisionfda-support@dnanexus.com", + "contactEmail": "fda-srs@fda.hhs.gov", "sessionExpirationWarning": { "extendSessionApiUrl": "/api/update_active", "maxSessionDurationMinutes": 15 diff --git a/src/app/fda/config/config.json b/src/app/fda/config/config.json index a16732a6c..4e365cfad 100644 --- a/src/app/fda/config/config.json +++ b/src/app/fda/config/config.json @@ -1,6 +1,6 @@ { "version": "3.1.1", - "contactEmail": "GSRSSupport@fda.hhs.gov", + "contactEmail": "fda-srs@fda.hhs.gov", "displayMatchApplication": "true", "adverseEventShinyHomepageDisplay": "true", "adverseEventShinySubstanceNameDisplay": "true", From 36212f85168a142378bf970b20684feda0fac2ff Mon Sep 17 00:00:00 2001 From: Petr Barta Date: Wed, 23 Apr 2025 11:16:18 +0200 Subject: [PATCH 037/408] Draft bulk submission --- .../substance-drafts.component.html | 92 +++++-- .../substance-drafts.component.scss | 99 +++++++ .../substance-drafts.component.ts | 249 ++++++++++++++++-- 3 files changed, 405 insertions(+), 35 deletions(-) diff --git a/src/app/core/substance-form/substance-drafts/substance-drafts.component.html b/src/app/core/substance-form/substance-drafts/substance-drafts.component.html index 27917d9e4..3c64aa446 100644 --- a/src/app/core/substance-form/substance-drafts/substance-drafts.component.html +++ b/src/app/core/substance-form/substance-drafts/substance-drafts.component.html @@ -1,16 +1,31 @@
- +
-
Saved Drafts
+
Saved Drafts
+
Drafts Validation
+
Submission Results
- - - +
+ +
+
+ +
+ + + + + @@ -37,18 +52,13 @@ -
Select + + Delete
+
No drafts were found for these conditions under local storage. Substance drafts are stored in this browser's cache, so using an incognito tab or clearing cache will clear or not allow access to drafts. You can save and load stored record drafts using the buttons below.
- - - - - - +
@@ -57,7 +67,7 @@ Show only current record
- +
Show only new registrations @@ -66,6 +76,9 @@
+ Save Backup @@ -74,8 +87,57 @@
{{filename? filename: 'no file chosen'}}
- +
-
\ No newline at end of file + + + + +
+ +
+
+ Type: {{draft.json.type}}, Name: {{ draft.json.name ? draft.json.name : '-' }} + This substance cannot be submitted +
+
+ + {{ message.messageType }} + + + {{ message.message }} +
+ {{ link.text }} +
+
+
+
+
+
+ +
+
+ +
+ +
+
+ Type: {{draft.json.type}}, Name: {{ draft.json.name ? draft.json.name : '-' }} +
+
+ ERROR + SUCCESS + + Could not be submitted due to validation errors + Submission in progress + View Substance + Error during submission process +
+
+
+
diff --git a/src/app/core/substance-form/substance-drafts/substance-drafts.component.scss b/src/app/core/substance-form/substance-drafts/substance-drafts.component.scss index cd265d50e..daa38e4e1 100644 --- a/src/app/core/substance-form/substance-drafts/substance-drafts.component.scss +++ b/src/app/core/substance-form/substance-drafts/substance-drafts.component.scss @@ -26,3 +26,102 @@ display: flex; flex-direction: row; } + +/* Drafts validation */ +.message { + display: flex; + align-items: center; +} + +.validation-body { + max-width: 95%; + display: flex; + max-width: 960px; + word-break: break-word; +} + +.validation-message { + display: flex; + padding: 5px 0; + + .message-type { + text-transform: uppercase; + font-weight: 500; + margin-right: 20px; + padding: 2px; + border-radius: 3px; + min-width: 80px; + } +} + +.warning-message { + color: var(--warning-dialog-color); + background-color: var(--warning-dialog-bg-color); + +} + +.error-message { + color: var(--error-dialog-color); + background-color: var(--error-dialog-bg-color); +} + +.notice-message { + color: var(--notice-dialog-color); + background-color: var(--notice-dialog-bg-color); + +} + +.spinner-overlay { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: rgba(255, 255, 255, 0.6); /* Light dim background */ + display: flex; + align-items: center; + justify-content: center; + z-index: 1000; /* Make sure it's above other content */ +} + +.draft-block { + margin-bottom: 20px; + padding-left: 8px; + border-left: 4px solid #ccc; +} + +.draft-block strong { + font-weight: bold; +} +.draft-block em { + color: #888; + font-style: italic; +} + +.message-row { + display: flex; + align-items: center; + margin: 6px 0; + padding-left: 12px; + gap: 8px; +} + +.label { + padding: 2px 6px; + border-radius: 4px; + font-size: 13px; + font-weight: 500; + white-space: nowrap; +} +.label-error { + background-color: #fce4e4; + color: #c62828; +} +.label-warning { + background-color: #fff8e1; + color: #f9a825; +} +.label-success { + background-color: #f3ffe1; + color: #13970a; +} diff --git a/src/app/core/substance-form/substance-drafts/substance-drafts.component.ts b/src/app/core/substance-form/substance-drafts/substance-drafts.component.ts index a1fa75743..0a567866a 100644 --- a/src/app/core/substance-form/substance-drafts/substance-drafts.component.ts +++ b/src/app/core/substance-form/substance-drafts/substance-drafts.component.ts @@ -5,9 +5,23 @@ import {MatDialogRef, MAT_DIALOG_DATA} from '@angular/material/dialog'; import { UtilsService } from '@gsrs-core/utils'; import { Sort } from '@angular/material/sort'; import { DomSanitizer } from '@angular/platform-browser'; -import { FormGroup } from '@angular/forms'; import { Router } from '@angular/router'; import * as moment from 'moment'; +import { ValidationMessage } from '@gsrs-core/substance-form/substance-form.model' + +enum SubmissionStatus { + NONE, + CANNOT_BE_SUBMITTED, + IN_PROGRESS, + SUCCESS, + ERROR +} + +enum FormState { + DRAFT_LIST, + VALIDATION_RESULTS, + SUBMISSION +} @Component({ selector: 'app-substance-drafts', @@ -16,23 +30,28 @@ import * as moment from 'moment'; }) export class SubstanceDraftsComponent implements OnInit { draft: SubstanceDraft; - displayedColumns: string[] = ['delete', 'type', 'name', 'uuid', 'date', 'load']; + displayedColumns: string[] = ['select', 'delete', 'type', 'name', 'uuid', 'date', 'load']; json: any; values: Array; filtered: Array; + selectedKeys: Array = []; onlyRegister = false; onlyCurrent = false; downloadJsonHref: any; fileName: string; - filename: string; - uploadForm: FormGroup; + filename: string; view = 'edit'; file: any; uuid: string; - + + formState: FormState = FormState.DRAFT_LIST; + isLoading: boolean = false; + validatedDrafts: Array = []; + constructor( private substanceFormService: SubstanceFormService, + private substanceService: SubstanceService, public dialogRef: MatDialogRef, private utilsService: UtilsService, private sanitizer: DomSanitizer, @@ -49,13 +68,11 @@ export class SubstanceDraftsComponent implements OnInit { this.uuid = this.json.uuid; } - + this.fetchDrafts(); const time = new Date().getTime(); this.fileName = 'gsrs-drafts-' + time; this.download(); - - } @@ -109,7 +126,6 @@ export class SubstanceDraftsComponent implements OnInit { useDraft(index) { - this.dialogRef.close(index); } @@ -126,15 +142,201 @@ export class SubstanceDraftsComponent implements OnInit { }); } + toggleDraft(draft: any, checked: boolean): void { + if (!this.selectedKeys.includes(draft.key) && checked) { + this.selectedKeys.push(draft.key); + } else if (this.selectedKeys.includes(draft.key) && !checked) { + this.selectedKeys = this.selectedKeys.filter(key => key !== draft.key); + } + } + + processValidationMessages(substanceCopy, results): void { + if (results.validationMessages) { + for (let i = 0; i < substanceCopy.references.length; i++) { + const ref = substanceCopy.references[i]; + if (ref.docType !== 'SYSTEM') { + if ((!ref.citation || ref.citation === '') || (!ref.docType || ref.docType === '')) { + const invalidReferenceMessage: ValidationMessage = { + actionType: 'frontEnd', + appliedChange: false, + links: [], + message: 'All references require a non-empty source type and text/citation value', + messageType: 'WARNING', + suggestedChange: true + }; + results.validationMessages.push(invalidReferenceMessage); + break; + } + } + } + if (substanceCopy.properties) { + for (let i = 0; i < substanceCopy.properties.length; i++) { + const prop = substanceCopy.properties[i]; + if (!prop.propertyType || !prop.name) { + const invalidPropertyMessage: ValidationMessage = { + actionType: 'frontEnd', + appliedChange: false, + links: [], + message: 'Property #' + (i + 1) + ' requires a non-empty name and type', + messageType: 'ERROR', + suggestedChange: true + }; + results.validationMessages.push(invalidPropertyMessage); + results.valid = false; + } + } + } + if (substanceCopy.relationships) { + for (let i = 0; i < substanceCopy.relationships.length; i++) { + const relationship = substanceCopy.relationships[i]; + if (!relationship.relatedSubstance || !relationship.type || relationship.type === '') { + const invalidRelationshipMessage: ValidationMessage = { + actionType: 'frontEnd', + appliedChange: false, + links: [], + message: 'Relationship #' + (i + 1) + ' requires a non-empty related substance and type', + messageType: 'ERROR', + suggestedChange: true + }; + results.validationMessages.push(invalidRelationshipMessage); + results.valid = false; + } + } + } + if (substanceCopy.polymer && substanceCopy.polymer.monomers) { + for (let i = 0; i < substanceCopy.polymer.monomers.length; i++) { + const prop = substanceCopy.polymer.monomers[i]; + if (!prop.monomerSubstance || prop.monomerSubstance == {}) { + const invalidPropertyMessage: ValidationMessage = { + actionType: 'frontEnd', + appliedChange: false, + links: [], + message: 'Monomer #' + (i + 1) + ' requires a selected substance', + messageType: 'ERROR', + suggestedChange: true + }; + results.validationMessages.push(invalidPropertyMessage); + results.valid = false; + } + } + } + if (substanceCopy.modifications && substanceCopy.modifications.physicalModifications) { + for (let i = 0; i < substanceCopy.modifications.physicalModifications.length; i++) { + const prop = substanceCopy.modifications.physicalModifications[i]; + let present = false; + if (prop && prop.parameters) { + prop.parameters.forEach(param => { + if (param.parameterName) { + present = true; + } + }); + } + + if (!prop.physicalModificationRole && !present) { + const invalidPropertyMessage: ValidationMessage = { + actionType: 'frontEnd', + appliedChange: false, + links: [], + message: 'Physical Modification #' + (i + 1) + ' requires a modification role or valid parameter', + messageType: 'ERROR', + suggestedChange: true + }; + results.validationMessages.push(invalidPropertyMessage); + results.valid = false; + } + } + } + } + } + + validateSelected(): void { + this.formState = FormState.VALIDATION_RESULTS; + this.isLoading = true; + this.validatedDrafts = []; + this.selectedKeys.forEach(key => { + const draft = JSON.parse(localStorage.getItem(key)); + const validatedDraft = { + key: key, + json: draft, + validationMessages: [], + validationResult: false, + submitStatus: SubmissionStatus.NONE, + fileUrl: undefined + } + + this.substanceService.validateSubstance(draft.substance).subscribe(results => { + + this.processValidationMessages(draft.substance, results); + validatedDraft.validationMessages = results.validationMessages.filter( + message => message.messageType.toUpperCase() === 'ERROR' || message.messageType.toUpperCase() === 'WARNING' || message.messageType.toUpperCase() === 'NOTICE' + ); + validatedDraft.validationResult = results.valid; + this.validatedDrafts.push(validatedDraft); + + if (this.validatedDrafts.length === this.selectedKeys.length) { + this.isLoading = false; + } + }, error => { + + validatedDraft.validationMessages.push({ + messageType: 'SERVER ERROR', + message: error.error?.message + + }) + + this.validatedDrafts.push(validatedDraft); + if (this.validatedDrafts.length === this.selectedKeys.length) { + this.isLoading = false; + } + }); + }) + } + + submitValid() { + this.formState = FormState.SUBMISSION; + this.isLoading = true; + this.validatedDrafts.forEach(draft => { + if (!draft.validationResult) { + draft.submitStatus = SubmissionStatus.CANNOT_BE_SUBMITTED; + } else { + draft.submitStatus = SubmissionStatus.IN_PROGRESS; + const result = { + isSuccessfull: false, + validationMessages: [], + serverError: undefined + } + this.substanceService.saveSubstance(draft.json.substance, 'import').subscribe(substance => { + draft.submitStatus = SubmissionStatus.SUCCESS; + draft.fileUrl = substance.fileUrl; + this.isLoading = false; + + }, error => { + draft.submitStatus = SubmissionStatus.ERROR; + result.isSuccessfull = false; + if (error && error.error && error.error.validationMessages) { + result.validationMessages = error.error.validationMessages; + } else { + result.serverError = error; + } + }); + } + }) + } + + + fixLink(link: string) { + return this.substanceService.oldLinkFix(link); + } + deleteDraft(draft: any): void { - localStorage.removeItem(draft.key); - this.filtered = this.filtered.filter(function( obj ) { - return obj.key !== draft.key; + localStorage.removeItem(draft.key); + this.filtered = this.filtered.filter(function( obj ) { + return obj.key !== draft.key; }); - this.values = this.values.filter(function( obj ) { - return obj.key !== draft.key; -}); + this.values = this.values.filter(function( obj ) { + return obj.key !== draft.key; + }); } @@ -156,7 +358,7 @@ export class SubstanceDraftsComponent implements OnInit { this.values = []; let keys = Object.keys(localStorage); let i = keys.length; - + while ( i-- ) { if (keys[i].startsWith('gsrs-draft-')){ const entry = JSON.parse(localStorage.getItem(keys[i])); @@ -168,7 +370,8 @@ export class SubstanceDraftsComponent implements OnInit { } this.filtered = this.values.sort((a, b) => { return b.date - a.date; - });; + }); + this.selectedKeys = []; if (this.json && this.json.uuid) { this.filterToggle('substance'); @@ -206,11 +409,17 @@ export class SubstanceDraftsComponent implements OnInit { } + hasValidDrafts(): boolean { + return this.validatedDrafts.some(draft => draft.validationResult) + } + close() { this.dialogRef.close(); } - + + public readonly SubmissionStatus = SubmissionStatus + public readonly FormState = FormState } @@ -221,9 +430,9 @@ export interface SubstanceDraft { uuid: any; date: string; type: string; - name?: string; + name?: string; substance: any; auto?: boolean; file?: any; fromNow?: string; -} \ No newline at end of file +} From ba040d21b68a7efb66271f847e10c4a39b2deea6 Mon Sep 17 00:00:00 2001 From: Petr Barta Date: Thu, 24 Apr 2025 15:09:52 +0200 Subject: [PATCH 038/408] Revert default FDA support email --- src/app/fda/config/config.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/fda/config/config.json b/src/app/fda/config/config.json index 4e365cfad..a16732a6c 100644 --- a/src/app/fda/config/config.json +++ b/src/app/fda/config/config.json @@ -1,6 +1,6 @@ { "version": "3.1.1", - "contactEmail": "fda-srs@fda.hhs.gov", + "contactEmail": "GSRSSupport@fda.hhs.gov", "displayMatchApplication": "true", "adverseEventShinyHomepageDisplay": "true", "adverseEventShinySubstanceNameDisplay": "true", From c9ea49be502bf08d2310494d216e885dc18ddde4 Mon Sep 17 00:00:00 2001 From: Petr Barta Date: Tue, 6 May 2025 12:42:04 +0300 Subject: [PATCH 039/408] missing property fixed --- .../session-expiration-dialog.component.ts | 1 - .../substance-edit-import-dialog.component.ts | 45 ++++++++++--------- 2 files changed, 23 insertions(+), 23 deletions(-) diff --git a/src/app/core/auth/session-expiration/session-expiration-dialog/session-expiration-dialog.component.ts b/src/app/core/auth/session-expiration/session-expiration-dialog/session-expiration-dialog.component.ts index 071c55c64..12d22c520 100644 --- a/src/app/core/auth/session-expiration/session-expiration-dialog/session-expiration-dialog.component.ts +++ b/src/app/core/auth/session-expiration/session-expiration-dialog/session-expiration-dialog.component.ts @@ -80,7 +80,6 @@ export class SessionExpirationDialogComponent implements OnInit { if (this.configService.configData.isPfdaVersion) { this.authService.pfdaLogin().pipe( concatMap(success => { - console.log('success: ', success); if (success) { this.closeDialog(); return this.authService.getAuth(); diff --git a/src/app/core/substance-edit-import-dialog/substance-edit-import-dialog.component.ts b/src/app/core/substance-edit-import-dialog/substance-edit-import-dialog.component.ts index f52eed7e7..5f5895bca 100644 --- a/src/app/core/substance-edit-import-dialog/substance-edit-import-dialog.component.ts +++ b/src/app/core/substance-edit-import-dialog/substance-edit-import-dialog.component.ts @@ -110,28 +110,29 @@ export class SubstanceEditImportDialogComponent implements OnInit { } } - useFile() { - if (!this.uploaded && this.pastedJSON) { - const read = JSON.parse(this.pastedJSON); - // If there is no substanceClass field in Substance JSON data - if (!read['substanceClass']) { - // if JSON data is from non-substance entity, read the json from the textbox - if (read['id']) { - this.loaded = true; - this.record = this.pastedJSON; - this.message = ''; - } else { - this.message = 'Error: Invalid JSON format'; - this.loaded = false; - } - } else { - this.loaded = true; - this.record = this.pastedJSON; - this.message = ''; - } - - } - } + // Is this method still used anywhere? + // useFile() { + // if (!this.uploaded && this.pastedJSON) { + // const read = JSON.parse(this.pastedJSON); + // // If there is no substanceClass field in Substance JSON data + // if (!read['substanceClass']) { + // // if JSON data is from non-substance entity, read the json from the textbox + // if (read['id']) { + // this.loaded = true; + // this.record = this.pastedJSON; + // this.message = ''; + // } else { + // this.message = 'Error: Invalid JSON format'; + // this.loaded = false; + // } + // } else { + // this.loaded = true; + // this.record = this.pastedJSON; + // this.message = ''; + // } + // + // } + // } checkLoaded() { this.loaded = true; From 1b86d7d87447f87c47f3c7acc7a1faa34003ce50 Mon Sep 17 00:00:00 2001 From: Mitch Miller Date: Tue, 6 May 2025 12:38:58 -0400 Subject: [PATCH 040/408] adding SSG1 and Mixtures to the list of substance types on the first page of the GSRS application. --- src/app/core/config/config.json | 10 ++++++++++ src/app/fda/config/config.json | 10 ++++++++++ 2 files changed, 20 insertions(+) diff --git a/src/app/core/config/config.json b/src/app/core/config/config.json index c50ccc603..ed5e90ecd 100644 --- a/src/app/core/config/config.json +++ b/src/app/core/config/config.json @@ -729,6 +729,16 @@ "display": "Concepts", "facetName": "Substance Class", "facetValue":"concept" + }, + { + "display": "Mixtures", + "facetName": "Substance Class", + "facetValue": "mixture" + }, + { + "display": "SSG1", + "facetName": "Substance Class", + "facetValue": "specifiedSubstanceG1" } ], "registrarDynamicLinks": [ diff --git a/src/app/fda/config/config.json b/src/app/fda/config/config.json index 380bb9b5f..aa17e47ed 100644 --- a/src/app/fda/config/config.json +++ b/src/app/fda/config/config.json @@ -1384,6 +1384,16 @@ "display": "Concepts", "facetName": "Substance Class", "facetValue": "concept" + }, + { + "display": "Mixtures", + "facetName": "Substance Class", + "facetValue": "mixture" + }, + { + "display": "SSG1", + "facetName": "Substance Class", + "facetValue": "specifiedSubstanceG1" } ], "registrarDynamicLinks": [ From 486ca48b2ed834d9620de1d2d406b9d63a9d4a8e Mon Sep 17 00:00:00 2001 From: Newatia Date: Tue, 6 May 2025 15:46:26 -0400 Subject: [PATCH 041/408] updated invitro and cross entity search --- src/app/core/base/base.component.html | 2 +- src/app/core/home/home.component.html | 6 +- .../core/registrars/registrars.component.html | 6 +- src/app/core/substance/substance.service.ts | 129 +++++++++++++----- .../substances-browse.component.ts | 13 +- src/app/fda/config/config.json | 16 +-- .../cross-entity-search.component.ts | 1 + ...invitro-pharmacology-browse.component.html | 14 +- .../invitro-pharmacology-browse.component.ts | 6 +- ...rmacology-details-testagent.component.html | 4 +- ...nvitro-pharmacology-details.component.html | 4 +- ...vitro-pharmacology-assay-form.component.ts | 25 ++-- ...ro-pharmacology-assayset-form.component.ts | 4 +- .../invitro-pharmacology-form.component.html | 2 +- .../invitro-pharmacology-form.component.ts | 24 ++-- ...o-pharmacology-summary-form.component.html | 2 +- ...tro-pharmacology-summary-form.component.ts | 28 ++-- ...ology-screening-data-import.component.html | 2 +- .../service/invitro-pharmacology.service.ts | 26 ++-- ...nvitro-pharmacology-summary.component.html | 2 +- ...stance-invitro-pharmacology.component.html | 2 +- .../substance-products.component.html | 4 +- 22 files changed, 192 insertions(+), 130 deletions(-) diff --git a/src/app/core/base/base.component.html b/src/app/core/base/base.component.html index 4194f0be6..25a8e951c 100644 --- a/src/app/core/base/base.component.html +++ b/src/app/core/base/base.component.html @@ -110,7 +110,7 @@ - Browse In-vitro Pharmacology + Browse In Vitro Pharmacology diff --git a/src/app/core/home/home.component.html b/src/app/core/home/home.component.html index e7309c757..6346120ad 100644 --- a/src/app/core/home/home.component.html +++ b/src/app/core/home/home.component.html @@ -98,7 +98,7 @@ - Browse In-vitro Pharmacology + Browse In Vitro Pharmacology @@ -239,14 +239,14 @@ - In-vitro Pharmacology Assay + In Vitro Pharmacology Assay - In-vitro Pharmacology Screening + In Vitro Pharmacology Screening diff --git a/src/app/core/registrars/registrars.component.html b/src/app/core/registrars/registrars.component.html index 372620069..326c208d2 100644 --- a/src/app/core/registrars/registrars.component.html +++ b/src/app/core/registrars/registrars.component.html @@ -76,7 +76,7 @@

- Browse In-vitro Pharmacology + Browse In Vitro Pharmacology @@ -179,14 +179,14 @@

- In-vitro Pharmacology Assay + In Vitro Pharmacology Assay - In-vitro Pharmacology Screening + In Vitro Pharmacology Screening diff --git a/src/app/core/substance/substance.service.ts b/src/app/core/substance/substance.service.ts index 36fd85f7b..7d3ac2f78 100644 --- a/src/app/core/substance/substance.service.ts +++ b/src/app/core/substance/substance.service.ts @@ -104,7 +104,7 @@ export class SubstanceService extends BaseHttpService { const uuid = []; if (content && content.length > 0) { content.forEach(substance => { - uuid.push(substance.uuid); + uuid.push(substance.uuid); }); } this.searchResult = {etag: result, uuids: uuid, total: total }; @@ -147,7 +147,11 @@ export class SubstanceService extends BaseHttpService { args.pageSize, args.facets, args.order, - args.skip + args.skip, + false, + args.simpleSearchOnly, + args.view, + args.viewfield ).subscribe(response => { observer.next(response); }, error => { @@ -156,7 +160,7 @@ export class SubstanceService extends BaseHttpService { observer.complete(); }); } else if ((args.sequenceSearchKey != null && args.sequenceSearchKey !== '') || - (args.sequenceSearchTerm != null && args.sequenceSearchTerm !== '')) { + (args.sequenceSearchTerm != null && args.sequenceSearchTerm !== '')) { this.searchSubstanceSequences( args.sequenceSearchTerm, args.sequenceSearchKey, @@ -167,7 +171,11 @@ export class SubstanceService extends BaseHttpService { args.pageSize, args.facets, args.order, - args.skip + args.skip, + true, + args.simpleSearchOnly, + args.view, + args.viewfield ).subscribe(response => { observer.next(response); }, error => { @@ -177,7 +185,7 @@ export class SubstanceService extends BaseHttpService { }); } else if ((args.bulkQID != null && args.bulkQID.toString() != '')) { this.searchSubstanceBulk( -// args.bulkSearchTerm, + // args.bulkSearchTerm, args.searchTerm, args.bulkQID, args.searchOnIdentifiers, @@ -275,10 +283,13 @@ export class SubstanceService extends BaseHttpService { facets?: FacetParam, order?: string, skip: number = 0, - sync: boolean = false + sync: boolean = false, + simpleSearchOnly?: boolean, + view?: string, + viewfield?: string ): Observable> { return new Observable(observer => { - let params = new FacetHttpParams({encoder: new CustomEncoder()}); + let params = new FacetHttpParams({ encoder: new CustomEncoder() }); let url = this.apiBaseUrl; let structureFacetsKey: number; structureFacetsKey = this.utilsService.hashCode(searchTerm, type, cutoff); @@ -332,6 +343,18 @@ export class SubstanceService extends BaseHttpService { url += 'substances/structureSearch'; } + if (simpleSearchOnly) { + params = params.append('simpleSearchOnly', simpleSearchOnly.toString()); // setting simpleSearchOnly=true, faster result, no facets + } + + if (view && view !== '') { + params = params.append('view', view); // setting view=key, faster result, no content + } + + if (viewfield && viewfield !== '') { + params = params.append('viewfield', viewfield); // setting view=key, faster result, no content + } + const options = { params: params }; @@ -376,10 +399,13 @@ export class SubstanceService extends BaseHttpService { facets?: FacetParam, order?: string, skip: number = 0, - sync: boolean = true + sync: boolean = true, + simpleSearchOnly?: boolean, + view?: string, + viewfield?: string ): Observable> { return new Observable(observer => { - let params = new FacetHttpParams({encoder: new CustomEncoder()}); + let params = new FacetHttpParams({ encoder: new CustomEncoder() }); let url = this.apiBaseUrl; let structureFacetsKey; @@ -408,6 +434,18 @@ export class SubstanceService extends BaseHttpService { seqType: seqType }); + if (simpleSearchOnly) { + params = params.append('simpleSearchOnly', simpleSearchOnly.toString()); // setting simpleSearchOnly=true, faster result, no facets + } + + if (view && view !== '') { + params = params.append('view', view); // setting view=key, faster result, no content + } + + if (viewfield && viewfield !== '') { + params = params.append('viewfield', viewfield); // setting view=key, faster result, no content + } + if (sync) { params = params.append('sync', sync.toString()); } @@ -447,7 +485,7 @@ export class SubstanceService extends BaseHttpService { } searchSubstanceBulk( -// bulkSearchTerm?: string, + // bulkSearchTerm?: string, querySearchTerm?: string, bulkQID?: number, searchOnIdentifiers?: boolean, @@ -458,16 +496,19 @@ export class SubstanceService extends BaseHttpService { facets?: FacetParam, order?: string, skip: number = 0, + simpleSearchOnly?: boolean, + view?: string, + viewfield?: string ): Observable> { return new Observable(observer => { - let params = new FacetHttpParams({encoder: new CustomEncoder()}); + let params = new FacetHttpParams({ encoder: new CustomEncoder() }); let url = this.apiBaseUrl; let bulkFacetsKey: number; bulkFacetsKey = this.utilsService.hashCode(bulkQID, searchOnIdentifiers, searchEntity); if (this.searchKeys[bulkFacetsKey]) { url += `status(${this.searchKeys[bulkFacetsKey]})/results`; params = params.appendFacetParams(facets, this.showDeprecated); - if(querySearchTerm.length > 0) { + if (querySearchTerm.length > 0) { params = params.appendDictionary({ top: pageSize.toString(), skip: skip.toString(), @@ -538,7 +579,9 @@ export class SubstanceService extends BaseHttpService { pageSize?: number, facets?: FacetParam, skip?: number, - view?: string + view?: string, + simpleSearchOnly?: boolean, + viewfield?: string ): void { this.tempObject = { querySearchTerm: querySearchTerm, @@ -550,12 +593,14 @@ export class SubstanceService extends BaseHttpService { pageSize: pageSize ? pageSize : 0, facets: facets ? facets : null, skip: skip ? skip : 0, - view: view ? view : null + view: view ? view : null, + simpleSearchOnly: simpleSearchOnly ? simpleSearchOnly : null, + viewfield: viewfield ? viewfield : null } - this.getAsyncSearchResults(querySearchTerm, searchKey, pageSize, facets, skip, view) + this.getAsyncSearchResults(querySearchTerm, searchKey, pageSize, facets, skip, view, simpleSearchOnly, viewfield) .pipe( switchMap(response => { - let temp:any = response; + let temp: any = response; temp.statusKey = searchKey; temp.finished = asyncCallResponse.finished; observer.next(temp); @@ -583,7 +628,9 @@ export class SubstanceService extends BaseHttpService { pageSize, facets, skip, - view + view, + simpleSearchOnly, + viewfield ); }); }, @@ -607,10 +654,12 @@ export class SubstanceService extends BaseHttpService { pageSize?: number, facets?: FacetParam, skip?: number, - view?: string + view?: string, + simpleSearchOnly?: boolean, + viewfield?: string ): any { const url = `${this.apiBaseUrl}status(${structureSearchKey})/results`; - let params = new FacetHttpParams({encoder: new CustomEncoder()}); + let params = new FacetHttpParams({ encoder: new CustomEncoder() }); params = params.appendFacetParams(facets, this.showDeprecated); @@ -624,6 +673,14 @@ export class SubstanceService extends BaseHttpService { view: view || '' }); + if (simpleSearchOnly) { + params = params.append('simpleSearchOnly', simpleSearchOnly.toString()); // setting simpleSearchOnly=true, faster result, no facets + } + + if (viewfield && viewfield !== '') { + params = params.append('viewfield', viewfield); // setting view=key, faster result, no content + } + // Added for 3.0.2, Advanced Search:Combine structure Search with query search. if (querySearchTerm != null && querySearchTerm !== '') { params = params.append('q', querySearchTerm); @@ -832,7 +889,7 @@ export class SubstanceService extends BaseHttpService { if (page === 'edit') { url = url + '/edit'; } - return url; + return url; } getSequenceByID(substance: string, unit: string, type: string): Observable { @@ -846,17 +903,17 @@ export class SubstanceService extends BaseHttpService { type?: string, seqType?: string, ): Observable { - let params = new FacetHttpParams(); - const url = this.apiBaseUrl + 'substances/sequenceSearch'; + let params = new FacetHttpParams(); + const url = this.apiBaseUrl + 'substances/sequenceSearch'; - params = params.appendDictionary({ - q: searchTerm, - type: type, - cutoff: cutoff.toString(), - seqType: seqType - }); + params = params.appendDictionary({ + q: searchTerm, + type: type, + cutoff: cutoff.toString(), + seqType: seqType + }); - return this.http.post(url, params); + return this.http.post(url, params); } oldLinkFix(link: string): string { @@ -873,9 +930,9 @@ export class SubstanceService extends BaseHttpService { //TODO: may need to url-encode some codeSystems for spaces/hyphens const refuuid = `${this.apiBaseUrl}substances(${reference.refuuid })/codes(codeSystem:` + codeSystem + `)(type:PRIMARY)($0)/code`; const refPname = `${this.apiBaseUrl}substances(${ reference.refPname })/codes(codeSystem:` + codeSystem + `)(type:PRIMARY)($0)/code`; - return this.http.get(refuuid).pipe( - catchError(error => this.http.get(refPname)) - ); + return this.http.get(refuuid).pipe( + catchError(error => this.http.get(refPname)) + ); } getPrimaryConfigCode(reference: SubstanceRelated): Observable { let cs: string; @@ -888,9 +945,9 @@ export class SubstanceService extends BaseHttpService { getBDNUM(reference: SubstanceRelated ): Observable { const refuuid = `${this.apiBaseUrl}substances(${reference.refuuid })/codes(codeSystem:BDNUM)(type:PRIMARY)($0)/code`; const refPname = `${this.apiBaseUrl}substances(${ reference.refPname })/codes(codeSystem:BDNUM)(type:PRIMARY)($0)/code`; - return this.http.get(refuuid).pipe( - catchError(error => this.http.get(refPname)) - ); + return this.http.get(refuuid).pipe( + catchError(error => this.http.get(refPname)) + ); } @@ -1050,7 +1107,7 @@ export class SubstanceService extends BaseHttpService { public GetStagedRecord(id:string) { let url = `${(this.configService.configData && this.configService.configData.apiBaseUrl) || '/' }api/v1/substances/stagingArea/${id}`; - + return this.http.get< any >(`${url}`); } diff --git a/src/app/core/substances-browse/substances-browse.component.ts b/src/app/core/substances-browse/substances-browse.component.ts index 7d865b711..299aab3e9 100644 --- a/src/app/core/substances-browse/substances-browse.component.ts +++ b/src/app/core/substances-browse/substances-browse.component.ts @@ -1356,7 +1356,7 @@ export class SubstancesBrowseComponent implements OnInit, AfterViewInit, OnDestr deprecated: this.showDeprecated, simpleSearchOnly: true, view: "key", - viewfield: "id" + // viewfield: "id" }).subscribe(pagingResponse => { if (pagingResponse.content) { @@ -1364,11 +1364,14 @@ export class SubstancesBrowseComponent implements OnInit, AfterViewInit, OnDestr // Loop through each substance and get Substance UUID if (results.length > 0) { - results.forEach((substanceUuid, index) => { + results.forEach((substance, index) => { // for Cross Entity Search, add Substance UUID in the temporary list - idListsTemp.push(substanceUuid); - - // Copy after the last record + if (substance) { + if (substance.idString) { + idListsTemp.push(substance.idString); + } + } + // Copy after the last record if (results.length == index + 1) { // For Cross Entity Search, copy idListTemp to idList after the loop so that change detection happens only once this.idLists = idListsTemp; diff --git a/src/app/fda/config/config.json b/src/app/fda/config/config.json index 713f5d176..6c8f1c7ac 100644 --- a/src/app/fda/config/config.json +++ b/src/app/fda/config/config.json @@ -122,7 +122,7 @@ }, { "card": "fda-substance-product", - "title": "Products, Applications, Clinical Trials, Adverse Events, Impurities Specs, SSG4 Manufacturing, In-vitro Pharmacology", + "title": "Products, Applications, Clinical Trials, Adverse Events, Impurities Specs, SSG4 Manufacturing, In Vitro Pharmacology", "filters": [ {} ] @@ -1196,7 +1196,7 @@ }, { "component": "invitropharmacology", - "display": "Browse In-vitro Pharmacology", + "display": "Browse In Vitro Pharmacology", "path": "browse-invitro-pharm", "order": 15 }, @@ -1235,37 +1235,37 @@ }, { "component": "invitropharmacology", - "display": "In-vitro Pharmacology Assay", + "display": "In Vitro Pharmacology Assay", "path": "invitro-pharm/assay/register", "order": 230 }, { "component": "invitropharmacology", - "display": "In-vitro Pharmacology Screening", + "display": "In Vitro Pharmacology Screening", "path": "invitro-pharm/register", "order": 240 }, { "component": "invitropharmacology", - "display": "In-vitro Pharmacology Summary", + "display": "In Vitro Pharmacology Summary", "path": "invitro-pharm/summary/register", "order": 250 }, { "component": "invitropharmacology", - "display": "In-vitro Pharmacology AssaySet Builder", + "display": "In Vitro Pharmacology AssaySet Builder", "path": "invitro-pharm/assaySetBuilder", "order": 260 }, { "component": "invitropharmacology", - "display": "Import In-vitro Pharm Assay", + "display": "Import In Vitro Pharm Assay", "path": "invitro-pharm/import/assay", "order": 270 }, { "component": "invitropharmacology", - "display": "Import In-vitro Pharm Screening", + "display": "Import In Vitro Pharm Screening", "path": "invitro-pharm/import/screening", "order": 280 } diff --git a/src/app/fda/cross-entity-search/cross-entity-search.component.ts b/src/app/fda/cross-entity-search/cross-entity-search.component.ts index 56aa0de7c..e9072bdec 100644 --- a/src/app/fda/cross-entity-search/cross-entity-search.component.ts +++ b/src/app/fda/cross-entity-search/cross-entity-search.component.ts @@ -186,6 +186,7 @@ export class CrossEntitySearchComponent implements OnInit { } else { // No record found this.statusMessage = "No related " + this.subEntityDisplay + " record Found. Please redefine your search criteria."; + // this.isSearchRunning = false; } } } diff --git a/src/app/fda/invitro-pharmacology/invitro-pharmacology-browse/invitro-pharmacology-browse.component.html b/src/app/fda/invitro-pharmacology/invitro-pharmacology-browse/invitro-pharmacology-browse.component.html index 3f942d65d..9044ac773 100644 --- a/src/app/fda/invitro-pharmacology/invitro-pharmacology-browse/invitro-pharmacology-browse.component.html +++ b/src/app/fda/invitro-pharmacology/invitro-pharmacology-browse/invitro-pharmacology-browse.component.html @@ -7,7 +7,7 @@ - +
    diff --git a/src/app/fda/invitro-pharmacology/invitro-pharmacology-form/invitro-pharmacology-form.component.ts b/src/app/fda/invitro-pharmacology/invitro-pharmacology-form/invitro-pharmacology-form.component.ts index eef4f328f..d04cf6adc 100644 --- a/src/app/fda/invitro-pharmacology/invitro-pharmacology-form/invitro-pharmacology-form.component.ts +++ b/src/app/fda/invitro-pharmacology/invitro-pharmacology-form/invitro-pharmacology-form.component.ts @@ -178,10 +178,10 @@ export class InvitroPharmacologyFormComponent implements OnInit, OnDestroy { // Get existing Assay record if (params['id']) { const id = params['id']; - this.title = 'Update In-vitro Pharmacology Screening'; + this.title = 'Update In Vitro Pharmacology Screening'; if (id !== this.id) { this.id = id; - this.titleService.setTitle(`Edit In-vitro Pharmacology Screening ` + this.id); + this.titleService.setTitle(`Edit In Vitro Pharmacology Screening ` + this.id); this.invitroPharmacologyService.loadAssay(); this.assay = this.invitroPharmacologyService.assay; @@ -192,7 +192,7 @@ export class InvitroPharmacologyFormComponent implements OnInit, OnDestroy { else if (this.activatedRoute.snapshot.queryParams['copyId']) { this.id = this.activatedRoute.snapshot.queryParams['copyId']; if (this.id) { //copy from existing Product - this.titleService.setTitle(`Register In-vitro Pharmacology from Copy ` + this.id); + this.titleService.setTitle(`Register In Vitro Pharmacology from Copy ` + this.id); this.title = 'Register New Invitro-Pharmacology Assay from Copy Assay Id ' + this.id; } } @@ -200,8 +200,8 @@ export class InvitroPharmacologyFormComponent implements OnInit, OnDestroy { else if (this.activatedRoute.snapshot.queryParams['action']) { let actionParam = this.activatedRoute.snapshot.queryParams['action']; if (actionParam && actionParam === 'import' && window.history.state) { - this.titleService.setTitle(`Register New In-vitro Pharmacology from Import`); - this.title = 'Register New In-vitro Pharmacology from Import'; + this.titleService.setTitle(`Register New In Vitro Pharmacology from Import`); + this.title = 'Register New In Vitro Pharmacology from Import'; const record = window.history.state.record; const response = JSON.parse(record); if (response) { @@ -222,13 +222,13 @@ export class InvitroPharmacologyFormComponent implements OnInit, OnDestroy { } } - // Register New In-vitro Pharamcology Screening Assay + // Register New In Vitro Pharamcology Screening Assay else { - this.title = 'Register New In-vitro Pharmacology Screening'; + this.title = 'Register New In Vitro Pharmacology Screening'; setTimeout(() => { // Create new Result Information Object to store Laboratory, Sponsor, Test Agents, Batch Number, Reference this.createResultInfoObject(); - this.titleService.setTitle(`Register In-vitro Pharmacology Screening`); + this.titleService.setTitle(`Register In Vitro Pharmacology Screening`); this.invitroPharmacologyService.loadAssay(); @@ -504,7 +504,7 @@ export class InvitroPharmacologyFormComponent implements OnInit, OnDestroy { this.loadingService.setLoading(this.isLoading); if (this.validationMessages.length === 0) { //&& results.valid === true) { - this.submissionMessage = 'Invitro Pharmacology Assay Screening is Valid. Would you like to submit?'; + this.submissionMessage = 'In Vitro Pharmacology Assay Screening is Valid. Would you like to submit?'; } // }, error => { //// this.addServerError(error); @@ -636,7 +636,7 @@ export class InvitroPharmacologyFormComponent implements OnInit, OnDestroy { private handleRecordRetrivalError() { const notification: AppNotification = { - message: 'The in-vitro pharmacology record you\'re trying to edit doesn\'t exist.', + message: 'The In Vitro pharmacology record you\'re trying to edit doesn\'t exist.', type: NotificationType.error, milisecondsToShow: 4000 }; @@ -966,7 +966,7 @@ export class InvitroPharmacologyFormComponent implements OnInit, OnDestroy { reloadPageAfterSave() { this.validationMessages = null; - this.submissionMessage = 'In-vitro Pharmacology Assay Screening data was saved successfully!'; + this.submissionMessage = 'In Vitro Pharmacology Assay Screening data was saved successfully!'; this.showSubmissionMessages = true; this.validationResult = false; @@ -988,7 +988,7 @@ export class InvitroPharmacologyFormComponent implements OnInit, OnDestroy { this.router.onSameUrlNavigation = 'reload'; this.router.navigate(['/invitro-pharm/', forwardId, 'edit']); } else { - alert("Something went wrong while retrieving the In-vitro Pharmacology Screening data"); + alert("Something went wrong while retrieving the In Vitro Pharmacology Screening data"); } this.isLoading = false; diff --git a/src/app/fda/invitro-pharmacology/invitro-pharmacology-form/invitro-pharmacology-summary-form/invitro-pharmacology-summary-form.component.html b/src/app/fda/invitro-pharmacology/invitro-pharmacology-form/invitro-pharmacology-summary-form/invitro-pharmacology-summary-form.component.html index 7eb82e014..ade0454f5 100644 --- a/src/app/fda/invitro-pharmacology/invitro-pharmacology-form/invitro-pharmacology-summary-form/invitro-pharmacology-summary-form.component.html +++ b/src/app/fda/invitro-pharmacology/invitro-pharmacology-form/invitro-pharmacology-summary-form/invitro-pharmacology-summary-form.component.html @@ -96,7 +96,7 @@ diff --git a/src/app/fda/invitro-pharmacology/invitro-pharmacology-form/invitro-pharmacology-summary-form/invitro-pharmacology-summary-form.component.ts b/src/app/fda/invitro-pharmacology/invitro-pharmacology-form/invitro-pharmacology-summary-form/invitro-pharmacology-summary-form.component.ts index ee3f97016..8a11e0552 100644 --- a/src/app/fda/invitro-pharmacology/invitro-pharmacology-form/invitro-pharmacology-summary-form/invitro-pharmacology-summary-form.component.ts +++ b/src/app/fda/invitro-pharmacology/invitro-pharmacology-form/invitro-pharmacology-summary-form/invitro-pharmacology-summary-form.component.ts @@ -151,11 +151,11 @@ export class InvitroPharmacologySummaryFormComponent implements OnInit, OnDestro // Get existing record if (params['id']) { const id = params['id']; - this.title = 'Update In-vitro Pharmacology Summary'; + this.title = 'Update In Vitro Pharmacology Summary'; if (id !== this.id) { this.id = id; this.testAgent = this.id; - this.titleService.setTitle(`Edit In-vitro Pharmacology Summary ` + this.id); + this.titleService.setTitle(`Edit In Vitro Pharmacology Summary ` + this.id); // Get Assays by Test Agent this.getTestAgentSummariesDetails(); } @@ -163,15 +163,15 @@ export class InvitroPharmacologySummaryFormComponent implements OnInit, OnDestro else if (this.activatedRoute.snapshot.queryParams['copyId']) { this.id = this.activatedRoute.snapshot.queryParams['copyId']; if (this.id) { //copy from existing Product - this.titleService.setTitle(`Register In-vitro Pharmacology from Copy ` + this.id); + this.titleService.setTitle(`Register In Vitro Pharmacology from Copy ` + this.id); this.title = 'Register New Invitro-Pharmacology Summary from Copy Assay Id ' + this.id; } } else if (this.activatedRoute.snapshot.queryParams['action']) { let actionParam = this.activatedRoute.snapshot.queryParams['action']; if (actionParam && actionParam === 'import' && window.history.state) { - this.titleService.setTitle(`Register New In-vitro Pharmacology from Import`); - this.title = 'Register New In-vitro Pharmacology from Import'; + this.titleService.setTitle(`Register New In Vitro Pharmacology from Import`); + this.title = 'Register New In Vitro Pharmacology from Import'; const record = window.history.state.record; const response = JSON.parse(record); if (response) { @@ -181,11 +181,11 @@ export class InvitroPharmacologySummaryFormComponent implements OnInit, OnDestro } } } - // Register New In-vitro Pharamcology Screening Summary + // Register New In Vitro Pharamcology Screening Summary else { - this.title = 'Register New In-vitro Pharmacology Summary'; + this.title = 'Register New In Vitro Pharmacology Summary'; setTimeout(() => { - this.titleService.setTitle(`Register In-vitro Pharmacology Summary`); + this.titleService.setTitle(`Register In Vitro Pharmacology Summary`); this.invitroPharmacologyService.loadAssay(); this.assay = this.invitroPharmacologyService.assay; @@ -236,7 +236,7 @@ export class InvitroPharmacologySummaryFormComponent implements OnInit, OnDestro }, error => { console.log('error'); const notification: AppNotification = { - message: 'There was an error trying to retrieve in-vitro pharmacology data. Please refresh and try again.', + message: 'There was an error trying to retrieve In Vitro pharmacology data. Please refresh and try again.', type: NotificationType.error, milisecondsToShow: 6000 }; @@ -392,7 +392,7 @@ export class InvitroPharmacologySummaryFormComponent implements OnInit, OnDestro this.isLoading = false; if (this.validationMessages.length === 0 && this.validationResult === true) { - this.submissionMessage = 'Invitro Pharmacology Assay Screening is Valid. Would you like to submit?'; + this.submissionMessage = 'In Vitro Pharmacology Assay Summary is Valid. Would you like to submit?'; } /* }, error => { this.addServerError(error); @@ -496,7 +496,7 @@ export class InvitroPharmacologySummaryFormComponent implements OnInit, OnDestro private handleRecordRetrivalError() { const notification: AppNotification = { - message: 'The in-vitro pharmacology record you\'re trying to edit doesn\'t exist.', + message: 'The In Vitro pharmacology record you\'re trying to edit doesn\'t exist.', type: NotificationType.error, milisecondsToShow: 4000 }; @@ -625,7 +625,7 @@ export class InvitroPharmacologySummaryFormComponent implements OnInit, OnDestro /* this.validationMessages = null; - this.submissionMessage = 'In-vitro Pharmacology Summary data was saved successfully!'; + this.submissionMessage = 'In Vitro Pharmacology Summary data was saved successfully!'; this.showSubmissionMessages = true; this.validationResult = false; @@ -658,7 +658,7 @@ export class InvitroPharmacologySummaryFormComponent implements OnInit, OnDestro reloadPageAfterSave() { this.validationMessages = null; - this.submissionMessage = 'In-vitro Pharmacology Summary data was saved successfully!'; + this.submissionMessage = 'In Vitro Pharmacology Summary data was saved successfully!'; this.showSubmissionMessages = true; this.validationResult = false; @@ -790,7 +790,7 @@ export class InvitroPharmacologySummaryFormComponent implements OnInit, OnDestro displayMessageAfterDeleteSummarities() { const dialogRef = this.dialog.open(ConfirmDialogComponent, { data: { - message: 'This in-vitro pharmacology assay screening record was deleted successfully', + message: 'This In Vitro pharmacology assay screening record was deleted successfully', type: 'home' } }); diff --git a/src/app/fda/invitro-pharmacology/invitro-pharmacology-screening-data-import/invitro-pharmacology-screening-data-import.component.html b/src/app/fda/invitro-pharmacology/invitro-pharmacology-screening-data-import/invitro-pharmacology-screening-data-import.component.html index 5545b25c0..4e4def7d0 100644 --- a/src/app/fda/invitro-pharmacology/invitro-pharmacology-screening-data-import/invitro-pharmacology-screening-data-import.component.html +++ b/src/app/fda/invitro-pharmacology/invitro-pharmacology-screening-data-import/invitro-pharmacology-screening-data-import.component.html @@ -397,7 +397,7 @@
Register Assay + matTooltip='Register In Vitro Pharmacology Assay'> Register Assay diff --git a/src/app/fda/invitro-pharmacology/service/invitro-pharmacology.service.ts b/src/app/fda/invitro-pharmacology/service/invitro-pharmacology.service.ts index c9f8c5047..3c33672c5 100644 --- a/src/app/fda/invitro-pharmacology/service/invitro-pharmacology.service.ts +++ b/src/app/fda/invitro-pharmacology/service/invitro-pharmacology.service.ts @@ -151,7 +151,7 @@ export class InvitroPharmacologyService extends BaseHttpService { return this.http.get(this.apiBaseUrlWithInvitroPharmEntityUrl + 'suggest?q=' + searchTerm); } - // Initialize or load data in In-vitro Pharmacology ASSAY ONLY + // Initialize or load data in In Vitro Pharmacology ASSAY ONLY loadAssayOnly(assay?: InvitroAssayInformation): void { // if Update/Exist Assay if (assay != null) { @@ -165,7 +165,7 @@ export class InvitroPharmacologyService extends BaseHttpService { } } - // Initialize or load data in In-vitro Pharmacology ASSAY + // Initialize or load data in In Vitro Pharmacology ASSAY loadAssay(assay?: InvitroAssayInformation): void { // if Update/Exist Assay if (assay != null) { @@ -185,7 +185,7 @@ export class InvitroPharmacologyService extends BaseHttpService { } } - // Initialize or load data in In-vitro Pharmacology ASSAY + // Initialize or load data in In Vitro Pharmacology ASSAY loadAssaySummaries(assay?: InvitroAssayInformation): void { // if Update/Exist Assay if (assay != null) { @@ -371,11 +371,11 @@ export class InvitroPharmacologyService extends BaseHttpService { 'Content-type': 'application/json' } }; - // Update In-vitro Pharmacology ASSAY + // Update In Vitro Pharmacology ASSAY if ((this.assay != null) && (this.assay.id)) { return this.http.put(url, this.assay, options); } else { - // Save New In-vitro Pharmacology ASSAY + // Save New In Vitro Pharmacology ASSAY return this.http.post(url, this.assay, options); } } @@ -390,11 +390,11 @@ export class InvitroPharmacologyService extends BaseHttpService { 'Content-type': 'application/json' } }; - // Update In-vitro Pharmacology ASSAY + // Update In Vitro Pharmacology ASSAY //if ((this.assay != null) && (this.assay.id)) { return this.http.put(url, this.assay, options); // } else { - // // Save New In-vitro Pharmacology ASSAY + // // Save New In Vitro Pharmacology ASSAY // return this.http.post(url, this.assay, options); // } } @@ -409,7 +409,7 @@ export class InvitroPharmacologyService extends BaseHttpService { 'Content-type': 'application/json' } }; - // Add or Update In-vitro Pharmacology ASSAY in Bulk + // Add or Update In Vitro Pharmacology ASSAY in Bulk return this.http.put(url, bulkAssays, options); } @@ -423,11 +423,11 @@ export class InvitroPharmacologyService extends BaseHttpService { 'Content-type': 'application/json' } }; - // Update In-vitro Pharmacology SCREENING + // Update In Vitro Pharmacology SCREENING if ((screening != null) && (screening.id)) { return this.http.put(url, screening, options); } else { - // Save New In-vitro Pharmacology SCREENING + // Save New In Vitro Pharmacology SCREENING return this.http.post(url, screening, options); } } @@ -445,11 +445,11 @@ export class InvitroPharmacologyService extends BaseHttpService { 'Content-type': 'application/json' } }; - // Update In-vitro Pharmacology Screening + // Update In Vitro Pharmacology Screening // if ((this.assay != null) && (this.assay.id)) { return this.http.post(url, screening, options); // } else { - // Save New In-vitro Pharmacology Screening + // Save New In Vitro Pharmacology Screening // return this.http.post(url, this.assay, options); // } } @@ -464,7 +464,7 @@ export class InvitroPharmacologyService extends BaseHttpService { 'Content-type': 'application/json' } }; - // Add or Update In-vitro Pharmacology ASSAY Screening in Bulk + // Add or Update In Vitro Pharmacology ASSAY Screening in Bulk return this.http.put(url, bulkScreenings, options); } diff --git a/src/app/fda/substance-details/substance-products/substance-invitro-pharmacology-summary/substance-invitro-pharmacology-summary.component.html b/src/app/fda/substance-details/substance-products/substance-invitro-pharmacology-summary/substance-invitro-pharmacology-summary.component.html index 570bf0cee..40193bab3 100644 --- a/src/app/fda/substance-details/substance-products/substance-invitro-pharmacology-summary/substance-invitro-pharmacology-summary.component.html +++ b/src/app/fda/substance-details/substance-products/substance-invitro-pharmacology-summary/substance-invitro-pharmacology-summary.component.html @@ -1,5 +1,5 @@
- In-vitro Pharmacology Summary + In Vitro Pharmacology Summary     diff --git a/src/app/fda/substance-details/substance-products/substance-invitro-pharmacology/substance-invitro-pharmacology.component.html b/src/app/fda/substance-details/substance-products/substance-invitro-pharmacology/substance-invitro-pharmacology.component.html index 28a1eddba..085f0c8a3 100644 --- a/src/app/fda/substance-details/substance-products/substance-invitro-pharmacology/substance-invitro-pharmacology.component.html +++ b/src/app/fda/substance-details/substance-products/substance-invitro-pharmacology/substance-invitro-pharmacology.component.html @@ -1,5 +1,5 @@
- In-vitro Pharmacology Screening + In Vitro Pharmacology Screening     diff --git a/src/app/fda/substance-details/substance-products/substance-products.component.html b/src/app/fda/substance-details/substance-products/substance-products.component.html index 3be28bf1e..932aa0391 100644 --- a/src/app/fda/substance-details/substance-products/substance-products.component.html +++ b/src/app/fda/substance-details/substance-products/substance-products.component.html @@ -236,13 +236,13 @@ - + - + From a9f894140d3f92476a98cfe789dd2ea255520909 Mon Sep 17 00:00:00 2001 From: Newatia Date: Thu, 8 May 2025 14:56:15 -0400 Subject: [PATCH 042/408] updated cross entity search --- src/app/core/substance/substance.service.ts | 58 ++++++--- .../substances-browse.component.html | 4 + .../substances-browse.component.ts | 120 ++++++++---------- .../cross-entity-search.component.ts | 77 ++++++++++- 4 files changed, 172 insertions(+), 87 deletions(-) diff --git a/src/app/core/substance/substance.service.ts b/src/app/core/substance/substance.service.ts index 7d3ac2f78..745cb077c 100644 --- a/src/app/core/substance/substance.service.ts +++ b/src/app/core/substance/substance.service.ts @@ -195,7 +195,10 @@ export class SubstanceService extends BaseHttpService { args.pageSize, args.facets, args.order, - args.skip + args.skip, + args.simpleSearchOnly, + args.view, + args.viewfield ).subscribe(response => { observer.next(response); }, error => { @@ -354,6 +357,7 @@ export class SubstanceService extends BaseHttpService { if (viewfield && viewfield !== '') { params = params.append('viewfield', viewfield); // setting view=key, faster result, no content } + const options = { params: params @@ -374,7 +378,10 @@ export class SubstanceService extends BaseHttpService { options, pageSize, facets, - skip + skip, + view, + simpleSearchOnly, + viewfield ); } else { observer.next(response); @@ -434,24 +441,24 @@ export class SubstanceService extends BaseHttpService { seqType: seqType }); - if (simpleSearchOnly) { - params = params.append('simpleSearchOnly', simpleSearchOnly.toString()); // setting simpleSearchOnly=true, faster result, no facets - } - - if (view && view !== '') { - params = params.append('view', view); // setting view=key, faster result, no content - } - - if (viewfield && viewfield !== '') { - params = params.append('viewfield', viewfield); // setting view=key, faster result, no content - } - if (sync) { params = params.append('sync', sync.toString()); } url += 'substances/sequenceSearch'; } + if (simpleSearchOnly) { + params = params.append('simpleSearchOnly', simpleSearchOnly.toString()); // setting simpleSearchOnly=true, faster result, no facets + } + + if (view && view !== '') { + params = params.append('view', view); // setting view=key, faster result, no content + } + + if (viewfield && viewfield !== '') { + params = params.append('viewfield', viewfield); // setting view=key, faster result, no content + } + const options = { params: params }; @@ -470,7 +477,10 @@ export class SubstanceService extends BaseHttpService { options, pageSize, facets, - skip + skip, + view, + simpleSearchOnly, + viewfield ); } else { observer.next(response); @@ -520,6 +530,7 @@ export class SubstanceService extends BaseHttpService { skip: skip.toString() }); } + if (order != null && order !== '') { params = params.append('order', order); } @@ -532,6 +543,18 @@ export class SubstanceService extends BaseHttpService { url += `substances/bulkSearch`; } + if (simpleSearchOnly) { + params = params.append('simpleSearchOnly', simpleSearchOnly.toString()); // setting simpleSearchOnly=true, faster result, no facets + } + + if (view && view !== '') { + params = params.append('view', view); // setting view=key, faster result, no content + } + + if (viewfield && viewfield !== '') { + params = params.append('viewfield', viewfield); // setting view=key, faster result, no content + } + const options = { params: params }; @@ -551,7 +574,10 @@ export class SubstanceService extends BaseHttpService { options, pageSize, facets, - skip + skip, + view, + simpleSearchOnly, + viewfield ); } else { // consider making API backend provide statusKey in JSON diff --git a/src/app/core/substances-browse/substances-browse.component.html b/src/app/core/substances-browse/substances-browse.component.html index b25f6cdb5..08edf14a1 100644 --- a/src/app/core/substances-browse/substances-browse.component.html +++ b/src/app/core/substances-browse/substances-browse.component.html @@ -13,6 +13,10 @@ diff --git a/src/app/core/substances-browse/substances-browse.component.ts b/src/app/core/substances-browse/substances-browse.component.ts index 299aab3e9..f5badd7db 100644 --- a/src/app/core/substances-browse/substances-browse.component.ts +++ b/src/app/core/substances-browse/substances-browse.component.ts @@ -296,7 +296,7 @@ export class SubstancesBrowseComponent implements OnInit, AfterViewInit, OnDestr // if cross entity search is performed, show the facets selected for Cross Entity/Sub Entity Search if (this.subEntitySearchHash) { - this.subEntityfacetDisplay(); + this.getCrossEntityParameters(); } this.subscriptions.push(authSubscription); @@ -961,6 +961,12 @@ export class SubstancesBrowseComponent implements OnInit, AfterViewInit, OnDestr queryParams: {} }; + // If performing cross entity search for Structure Search, call this to return to Structure editor + // with populated structure + if (this.subEntitySearchHash) { + this.getCrossEntityParameters('structureSearch'); + } + navigationExtras.queryParams['structure'] = this.privateStructureSearchTerm || null; navigationExtras.queryParams['type'] = this.privateSearchType || null; @@ -983,6 +989,11 @@ export class SubstancesBrowseComponent implements OnInit, AfterViewInit, OnDestr this.smiles = ''; this.pageIndex = 0; + // Clear Cross Entity Search Criteria display + if (this.subEntityDisplayFacets) { + this.subEntityDisplayFacets = null; + } + this.populateUrlQueryParameters(); this.searchSubstances(); } @@ -1005,6 +1016,12 @@ export class SubstancesBrowseComponent implements OnInit, AfterViewInit, OnDestr queryParams: {} }; + // If performing cross entity search for Structure Search, call this to return to Structure editor + // with populated structure + if (this.subEntitySearchHash) { + this.getCrossEntityParameters('sequenceSearch'); + } + navigationExtras.queryParams['type'] = this.privateSearchType || null; navigationExtras.queryParams['cutoff'] = this.privateSearchCutoff || 0; navigationExtras.queryParams['seq_type'] = this.privateSearchSeqType || null; @@ -1028,6 +1045,11 @@ export class SubstancesBrowseComponent implements OnInit, AfterViewInit, OnDestr this.privateSearchSeqType = ''; this.pageIndex = 0; + // Clear Cross Entity Search Criteria display + if (this.subEntityDisplayFacets) { + this.subEntityDisplayFacets = null; + } + this.populateUrlQueryParameters(); this.searchSubstances(); } @@ -1099,6 +1121,11 @@ export class SubstancesBrowseComponent implements OnInit, AfterViewInit, OnDestr this.clearSearch(); } this.facetManagerService.clearSelections(); + + // Clear Cross Entity Search Criteria display + if (this.subEntityDisplayFacets) { + this.subEntityDisplayFacets = null; + } } clickToRefreshPreview() { @@ -1331,7 +1358,6 @@ export class SubstancesBrowseComponent implements OnInit, AfterViewInit, OnDestr // Need this for Cross Entity Search. Return all the IDs only for the current search getSearchIdsOnly(doPerformSearch: boolean) { - if (doPerformSearch) { let idListsTemp: Array = []; @@ -1392,7 +1418,9 @@ export class SubstancesBrowseComponent implements OnInit, AfterViewInit, OnDestr } } - subEntityfacetDisplay() { + getCrossEntityStorageItem(): any { + let searchParamItems: any; + // if (hashcode is found on the url) if (this.subEntitySearchHash) { // Get Sub-entity facet values from local Storage to display on Browse Substance page @@ -1400,84 +1428,48 @@ export class SubstancesBrowseComponent implements OnInit, AfterViewInit, OnDestr if (searchParams) { const searchParamItems = JSON.parse(searchParams); - if (searchParamItems) { - this.subEntity = searchParamItems['subEntityDisplay']; - this.subEntityDisplayFacets = searchParamItems['subEntityDisplayFacets']; - } + return searchParamItems; } } } - editSubEntitySearch(): void { - if (this.subEntitySearchHash) { - /* - const searchParam: Array = []; + getCrossEntityParameters(displayParam?: string) { + let searchParamItems = this.getCrossEntityStorageItem(); - const searchParams = localStorage.getItem(this.subEntitySearchHash); + if (searchParamItems) { + this.subEntity = searchParamItems['subEntityDisplay']; + this.subEntityDisplayFacets = searchParamItems['subEntityDisplayFacets']; + } - if (searchParams) { - const searchParamItems = JSON.parse(searchParams); - if (searchParamItems) { - this.subEntity = searchParamItems['subEntity']; - this.subEntityDisplayFacets = searchParamItems['subEntityDisplayFacets']; + // Get the Structure Search Term from Storage + if (displayParam) { + if (displayParam == 'structureSearch') { + this.privateStructureSearchTerm = searchParamItems['thisEntityStructureSearchTerm']; + + if (searchParamItems['thisEntityCutoff']) { + this.privateSearchCutoff = searchParamItems['thisEntityCutoff']; } - } - */ + } + if (displayParam == 'sequenceSearch') { + this.privateSequenceSearchTerm = searchParamItems['thisEntitySequenceSearchTerm']; + } + } + } + + editSubEntitySearch(): void { + if (this.subEntitySearchHash) { let randomInteger = Math.floor(Math.random() * 100000000); // Using random number to create different value, so it will trigger change Detection for Input + + //OUTPUT - EMIT, trigger to display the sub entity facets again this.editSubEntitySearchHash = this.subEntitySearchHash + '_' + randomInteger; } - - /* - const queryStatementHashes = []; - const queryStatement = { - condition: '', - queryableProperty: 'Manual Query Entry', - command: 'Manual Query Entry', - commandInputValues: advSearchTerm, - query: this.privateSearchTerm - }; - - // Store in cookies, Category tab (Substance, Application, etc) - const categoryHash = this.utilsService.hashCode('Substance'); - localStorage.setItem(categoryHash.toString(), 'Substance'); - queryStatementHashes.push(categoryHash); - - const queryStatementString = JSON.stringify(queryStatement); - const hash = this.utilsService.hashCode(queryStatementString); - - // Store in cookies, Each Query Statement is stored in separate hash - localStorage.setItem(hash.toString(), queryStatementString); - - // Push Query Statements Hashes in Array - queryStatementHashes.push(hash); - - // Store in cookies, store in Query Hash - Query Statement Hashes Array - const queryStatementHashesString = JSON.stringify(queryStatementHashes); - - localStorage.setItem(this.searchTermHash.toString(), queryStatementHashesString); - } - - // ** END: Store in Local Storage - */ - const navigationExtras: NavigationExtras = { queryParams: { 'subentity-hash': this.searchTermHash } }; - - /* - navigationExtras.queryParams['structure'] = this.privateStructureSearchTerm || null; - navigationExtras.queryParams['type'] = this.privateSearchType || null; - - if (this.privateSearchType === 'similarity') { - navigationExtras.queryParams['cutoff'] = this.privateSearchCutoff || 0; - } - */ - - // this.router.navigate(['/advanced-search'], navigationExtras); } } \ No newline at end of file diff --git a/src/app/fda/cross-entity-search/cross-entity-search.component.ts b/src/app/fda/cross-entity-search/cross-entity-search.component.ts index e9072bdec..6d4b3e66d 100644 --- a/src/app/fda/cross-entity-search/cross-entity-search.component.ts +++ b/src/app/fda/cross-entity-search/cross-entity-search.component.ts @@ -75,6 +75,11 @@ export class CrossEntitySearchComponent implements OnInit { // Needed for cross/sub entity search thisEntitySearchTerm = null; thisEntityFacetString = ''; + thisEntitySmiles = null; + thisEntityType = null; + thisEntityCutoff: number; + thisEntityStructureSearchTerm = null; + thisEntitySequenceSearchTerm = null; thisEntityFacetParams: FacetParam; thisEntityDisplayFacets: Array = []; editSubEntitySearchHashCode = null; @@ -142,6 +147,31 @@ export class CrossEntitySearchComponent implements OnInit { this.thisEntitySearchTerm = entSearchTerm; } + @Input() + set entitySmiles(entSmiles) { + this.thisEntitySmiles = entSmiles; + } + + @Input() + set entityType(entType) { + this.thisEntityType = entType; + } + + @Input() + set entityCutoff(entCutoff) { + this.thisEntityCutoff = entCutoff; + } + + @Input() + set entityStructureSearchTerm(entStructureSearchTerm) { + this.thisEntityStructureSearchTerm = entStructureSearchTerm; + } + + @Input() + set entitySequenceSearchTerm(entSequenceSearchTerm) { + this.thisEntitySequenceSearchTerm = entSequenceSearchTerm; + } + @Input() set entityFacetParams(entFacetParams) { this.thisEntityFacetParams = entFacetParams; @@ -186,7 +216,7 @@ export class CrossEntitySearchComponent implements OnInit { } else { // No record found this.statusMessage = "No related " + this.subEntityDisplay + " record Found. Please redefine your search criteria."; - // this.isSearchRunning = false; + this.isSearchRunning = false; } } } @@ -349,7 +379,7 @@ export class CrossEntitySearchComponent implements OnInit { const facetKeysToRemove = Object.keys(this.removePrivateFacetParams); facetKeysToRemove.forEach(key => { delete this.privateFacetParams[key]; - }); + }); } // if facet popup dialog is open @@ -708,12 +738,10 @@ export class CrossEntitySearchComponent implements OnInit { getBulkSearchTotal(entity: string, key: number, useServiceInUrl: boolean) { let qTop = 1000000; let count = 0; - console.log("GET TOTAL "); if (entity === this.ENTITY_SUBSTANCE) { this.bulkSearchTotal = this.idListForSearch.length; } else { - console.log("GET TOTAL IN ELSE IN ELSE"); // ** Perform BULK SEARCH STATUS RESULTS ** this.crossEntitySearchService.getBulkSearchStatusResults(entity, key, 10, null, null, null, useServiceInUrl, null, this.privateFacetParams, 10, 0, 1000000).subscribe(response => { if (response) { @@ -797,7 +825,13 @@ export class CrossEntitySearchComponent implements OnInit { let item = { 'entity': this.entity, 'subEntityDisplay': this.subEntityDisplay, + 'thisEntitySmile': this.thisEntitySmiles, + 'thisEntityType': this.thisEntityType, + 'thisEntityCutoff': this.thisEntityCutoff, + 'thisEntityStructureSearchTerm': this.thisEntityStructureSearchTerm, + 'thisEntitySequenceSearchTerm': this.thisEntitySequenceSearchTerm, 'thisEntityFacetParams': this.thisEntityFacetParams, + 'thisEntityDisplayFacets': this.thisEntityDisplayFacets, 'subEntityDisplayFacets': this.subEntityDisplayFacets, 'idListForSearch': this.idListForSearchOld, 'thisEntityTotalRecords': this.thisEntityTotalRecords, @@ -833,6 +867,18 @@ export class CrossEntitySearchComponent implements OnInit { navigationExtras.queryParams['search'] = this.thisEntitySearchTerm || null; } + if (this.thisEntityType) { + navigationExtras.queryParams['type'] = this.thisEntityType || null; + } + + if (this.thisEntityCutoff) { + navigationExtras.queryParams['cutoff'] = this.thisEntityCutoff || null; + } + + if (this.thisEntitySmiles) { + navigationExtras.queryParams['smiles'] = this.thisEntitySmiles || null; + } + if (this.thisEntityFacetString) { navigationExtras.queryParams['facets'] = this.thisEntityFacetString; } @@ -888,6 +934,11 @@ export class CrossEntitySearchComponent implements OnInit { this.secondIdListForSearch = searchParamItems['secondIdListForSearch']; + this.thisEntityFacetParams = searchParamItems['thisEntityFacetParams']; + this.thisEntityDisplayFacets = searchParamItems['thisEntityDisplayFacets']; + + this.privateFacetParams = null; + // Get Object that is selected in the dropdown let subEntity = this.entityLists.find(ent => ent.entityDisplay === this.subEntityDisplay); @@ -907,11 +958,23 @@ export class CrossEntitySearchComponent implements OnInit { //this.idListForSearchOld = searchParamItems['idListForSearch']; // this.idListForSearch = searchParamItems['idListForSearch']; this.bulkQID = searchParamItems['facetBulkQID']; - this.rawFacets = searchParamItems['rawFacets']; - } + // Get Bulk Substance Key for bulkQID + if (this.bulkQID) { + this.crossEntitySearchService.getBulkSearchWithFacets(this.subEntityEndpoint, this.bulkQID, this.bulkSearchUrl, this.searchOnIdentifiers, this.privateFacetParams, 'key', this.useServiceInUrl).subscribe(response => { + const searchResults: any = response; + + if (searchResults) { + this.bulkSearchKey = searchResults.key; + + // Set Facets + this.rawFacets = searchParamItems['rawFacets']; + + } + }); + } - //this.idLists = this.idListForSearch; + } // Open Facets Popup this.openModal(); From 2d93b18e34e6429609124459986bc18e4e345dfb Mon Sep 17 00:00:00 2001 From: Newatia Date: Thu, 8 May 2025 17:36:11 -0400 Subject: [PATCH 043/408] set loading false in name search and impurities --- .../advanced-selector-dialog.component.ts | 5 ++++- .../impurities-form/impurities-form.component.ts | 7 +++---- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/app/core/substance-selector/advanced-selector-dialog/advanced-selector-dialog.component.ts b/src/app/core/substance-selector/advanced-selector-dialog/advanced-selector-dialog.component.ts index 6f9d25871..98066dc86 100644 --- a/src/app/core/substance-selector/advanced-selector-dialog/advanced-selector-dialog.component.ts +++ b/src/app/core/substance-selector/advanced-selector-dialog/advanced-selector-dialog.component.ts @@ -379,8 +379,11 @@ private privateSequenceSearchKey?: string; this.loading = false; }); + }, error => { + this.loading = false; + this.loadingService.setLoading(false); + console.log("error getting name in function searchSubstances()"); }); - } openStructureImportDialog(): void { diff --git a/src/app/fda/impurities/impurities-form/impurities-form.component.ts b/src/app/fda/impurities/impurities-form/impurities-form.component.ts index 83d86b22b..591266c03 100644 --- a/src/app/fda/impurities/impurities-form/impurities-form.component.ts +++ b/src/app/fda/impurities/impurities-form/impurities-form.component.ts @@ -25,6 +25,7 @@ import { ConfigService } from '@gsrs-core/config'; import { SubstanceEditImportDialogComponent } from '@gsrs-core/substance-edit-import-dialog/substance-edit-import-dialog.component'; import { JsonDialogFdaComponent } from '../../json-dialog-fda/json-dialog-fda.component'; import { ConfirmDialogComponent } from '../../confirm-dialog/confirm-dialog.component'; +import { SubstanceFormResults } from '@gsrs-core/substance-form/substance-form.model'; @Component({ selector: 'app-impurities-form', @@ -502,9 +503,7 @@ export class ImpuritiesFormComponent implements OnInit, OnDestroy { this.router.navigate(['/impurities', id]); } }, 4000); - } - /* - , (error: SubstanceFormResults) => { + }, (error: SubstanceFormResults) => { this.showSubmissionMessages = true; this.loadingService.setLoading(false); this.isLoading = false; @@ -522,7 +521,7 @@ export class ImpuritiesFormComponent implements OnInit, OnDestroy { this.submissionMessage = null; }, 8000); } - }*/ + } ); } From 760a336290e6c47b93f90d1fdffbdf9517f333fb Mon Sep 17 00:00:00 2001 From: alx652 <58182629+alx652@users.noreply.github.com> Date: Wed, 14 May 2025 18:59:23 -0400 Subject: [PATCH 044/408] Update process-index.js --- process-index.js | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/process-index.js b/process-index.js index 4351cb887..96510a4fb 100644 --- a/process-index.js +++ b/process-index.js @@ -24,6 +24,15 @@ fs.readFile(indexFilePath, 'utf8', function (err, data) { } }); + const scriptAnalyticsCustom = $('script#analytics-custom'); + scriptAnalyticsCustom.each((index, _element) => { + const element = $(_element); + if (element.attr('src') && element.attr('src').match(/^\/assets\//) || element.attr('src').match(/^assets\//)) { + const currentHref = element.attr('src').replace(/^\//, ''); + element.attr('src', baseHref + currentHref); + } + }); + const metas = $('meta'); links.each((index, _element) => { const element = $(_element); @@ -38,4 +47,4 @@ fs.readFile(indexFilePath, 'utf8', function (err, data) { fs.writeFile(indexFilePath, $.html(), function (err) { if (err) return console.log(err); }); -}); \ No newline at end of file +}); From cb7904811c983410b6598dde42a90b52eecb2672 Mon Sep 17 00:00:00 2001 From: alx652 <58182629+alx652@users.noreply.github.com> Date: Wed, 14 May 2025 19:03:07 -0400 Subject: [PATCH 045/408] Update index.html --- src/index.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/index.html b/src/index.html index dfe4d8f6f..277df7c9c 100644 --- a/src/index.html +++ b/src/index.html @@ -2,7 +2,7 @@ - + From 6abcca6975eccec629c0aefdcf8242e06ce40791 Mon Sep 17 00:00:00 2001 From: alx652 <58182629+alx652@users.noreply.github.com> Date: Fri, 6 Jun 2025 18:23:32 -0400 Subject: [PATCH 046/408] Update show-application-toggle.component.ts --- .../show-application-toggle.component.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/app/fda/substance-browse/show-application-toggle/show-application-toggle.component.ts b/src/app/fda/substance-browse/show-application-toggle/show-application-toggle.component.ts index ae7608b23..a6c0d19dc 100644 --- a/src/app/fda/substance-browse/show-application-toggle/show-application-toggle.component.ts +++ b/src/app/fda/substance-browse/show-application-toggle/show-application-toggle.component.ts @@ -109,21 +109,26 @@ export class ShowApplicationToggleComponent implements OnInit, AfterViewInit, On const url = this.getApiExportUrl(this.etag, extension, source); if (this.isAdmin === true) { let type = ''; + let entity = ''; if (source != null) { if (source === 'app') { type = 'browseSubstanceApplication'; + entity = 'applications'; } else if (source === 'prod') { type = 'browseSubstanceProduct'; + entity = 'products'; } else if (source === 'clinicaltrialsus') { type = 'browseSubstanceClinicalTrial-US'; + entity = 'clinicaltrialsus'; } else if (source === 'clinicaltrialseurope') { type = 'browseSubstanceClinicalTrial-EU'; + entity = 'clinicaltrialseurope'; } } const dialogReference = this.dialog.open(ExportDialogComponent, { // height: '215x', width: '700px', - data: { 'extension': extension, 'type': type, 'entity': 'applications', 'hideOptionButtons': true } + data: { 'extension': extension, 'type': type, 'entity': entity, 'hideOptionButtons': true } }); dialogReference.afterClosed().subscribe(response => { // this.overlayContainer.style.zIndex = null; From e1c40b4fe87888fa4e2e82a8e05065449b629e37 Mon Sep 17 00:00:00 2001 From: Newatia Date: Mon, 9 Jun 2025 09:28:01 -0400 Subject: [PATCH 047/408] added features for 3.1.3 --- .../application-form.component.ts | 12 ++- .../applications-browse.component.html | 16 ++-- .../applications-browse.component.ts | 78 +++++++++++++++-- .../cross-entity-search.component.ts | 1 - .../products-browse.component.html | 9 +- .../products-browse.component.ts | 84 ++++++++++++++++++- .../fda/product/service/product.service.ts | 2 +- 7 files changed, 178 insertions(+), 24 deletions(-) diff --git a/src/app/fda/application/application-form/application-form.component.ts b/src/app/fda/application/application-form/application-form.component.ts index 39347dcdc..ca74f7889 100644 --- a/src/app/fda/application/application-form/application-form.component.ts +++ b/src/app/fda/application/application-form/application-form.component.ts @@ -211,6 +211,16 @@ export class ApplicationFormComponent implements OnInit, AfterViewInit, OnDestro this.setValidationMessage('Application Number is required'); } + // Validate Center and do not allow CDER and CBER data to register new record + if (this.application.center) { + // if registering a new record + if (!this.id) { + if (this.application.center === 'CDER' || this.application.center === 'CBER') { + this.setValidationMessage(this.application.center + ' center is not allowed to register a new record'); + } + } + } + // Validate Submit Date in application if ((this.submitDateMessage !== null) && (this.submitDateMessage.length > 0)) { this.setValidationMessage(this.submitDateMessage); @@ -461,7 +471,7 @@ export class ApplicationFormComponent implements OnInit, AfterViewInit, OnDestro let cleanApplication = this.cleanApplication(); - let data = {jsonData: cleanApplication, jsonFilename: jsonFilename}; + let data = { jsonData: cleanApplication, jsonFilename: jsonFilename }; const dialogRef = this.dialog.open(JsonDialogFdaComponent, { width: '90%', diff --git a/src/app/fda/application/applications-browse/applications-browse.component.html b/src/app/fda/application/applications-browse/applications-browse.component.html index 069dbb339..621ed6463 100644 --- a/src/app/fda/application/applications-browse/applications-browse.component.html +++ b/src/app/fda/application/applications-browse/applications-browse.component.html @@ -245,16 +245,12 @@ eventCategory="applicationSearch"> - - +
+ +
diff --git a/src/app/fda/application/applications-browse/applications-browse.component.ts b/src/app/fda/application/applications-browse/applications-browse.component.ts index 71b1c6957..d8d5fc04e 100644 --- a/src/app/fda/application/applications-browse/applications-browse.component.ts +++ b/src/app/fda/application/applications-browse/applications-browse.component.ts @@ -14,6 +14,7 @@ import { Sort } from '@angular/material/sort'; import { LoadingService } from '@gsrs-core/loading'; import { MainNotificationService } from '@gsrs-core/main-notification'; import { AppNotification, NotificationType } from '@gsrs-core/main-notification'; +import { BulkSearchService } from '@gsrs-core/bulk-search/service/bulk-search.service'; import { ConfigService } from '@gsrs-core/config'; import { AuthService } from '@gsrs-core/auth/auth.service'; import { UtilsService } from '@gsrs-core/utils/utils.service'; @@ -113,6 +114,7 @@ export class ApplicationsBrowseComponent implements OnInit, AfterViewInit, OnDes private subscriptions: Array = []; constructor( + public bulkSearchService: BulkSearchService, public applicationService: ApplicationService, public generalService: GeneralService, private activatedRoute: ActivatedRoute, @@ -247,8 +249,8 @@ export class ApplicationsBrowseComponent implements OnInit, AfterViewInit, OnDes // below export statement //this.dataSource = this.applications; - // if Bulk Search is not finished - if (pagingResponse.finished) { + // if Bulk Search is not finished + if (pagingResponse.finished) { this.isSearchFinished = true; } @@ -680,8 +682,68 @@ export class ApplicationsBrowseComponent implements OnInit, AfterViewInit, OnDes this.overlayContainer.style.zIndex = null; } + createQueryTextWithIds(idListForSearch: Array, entity: string, rootId: boolean = false): string { + let queryText = ''; + + idListForSearch.forEach((id, index) => { + if (id) { + if (index > 0) { + queryText = queryText + '\n'; + } + if (entity && entity === 'substances') { + queryText = queryText + 'root_uuid:"' + id + '"'; + } else { + if (rootId) { + queryText = queryText + 'root_id:"' + id + '"'; + } else { + queryText = queryText + 'entity_link_substances:"' + id + '"'; + } + } + } + }); + + return queryText; + } + + getBulkQuery(substanceIdLists: Array, entity: string) { + let queryText = this.createQueryTextWithIds(substanceIdLists, entity); + + if (queryText) { + this.bulkSearchService.postOrPutBulkQuery(entity, queryText).subscribe(result => { + if (result) { + if (result.id) { + this.forwardToSubstance(result.id); + } + } + }); + } + } + + showAllSubstances() { + this.getSearchIdsOnly(true, "all substances"); + } + + forwardToSubstance(bulkQID: number) { + // Store current url in cookies + + const searchItemHash = this.utilsService.hashCode(); + + // Store parameters in local storage + // localStorage.setItem(searchItemHash.toString(), JSON.stringify(item)); + + const navigationExtras: NavigationExtras = { + queryParams: {} + }; + + if (bulkQID > 0) { + navigationExtras.queryParams['bulkQID'] = bulkQID; + } + + this.router.navigate(['/browse-substance'], navigationExtras); + } + // Need this for Cross Entity Search. Return all the IDs only for the current search - getSearchIdsOnly(doPerformSearch: boolean) { + getSearchIdsOnly(doPerformSearch: boolean = true, searchType?: string) { if (doPerformSearch) { let idListsTemp: Array = []; @@ -714,8 +776,12 @@ export class ApplicationsBrowseComponent implements OnInit, AfterViewInit, OnDes // Copy after the last record if (response.content.length == index + 1) { - // Get Search Product Ids - this.getSearchApplicationIds(idListsTemp); + if (searchType && searchType === 'all substances') { + this.getBulkQuery(idListsTemp, 'substances',); + } else { + // Get Search Product Ids for Cross Entity Search + this.getSearchApplicationIds(idListsTemp); + } // For Cross Entity Search, copy idListTemp to idList after the loop so that change detection happens only once // this.idLists = idListsTemp; @@ -808,7 +874,7 @@ export class ApplicationsBrowseComponent implements OnInit, AfterViewInit, OnDes // Get Product records that have Ingredients this.privateFacetParams['Has Ingredients'] = { 'params': { 'Has Ingredients': true }, 'isAllMatch': false }; - + const subscription = this.applicationService.getApplications( order, skip, diff --git a/src/app/fda/cross-entity-search/cross-entity-search.component.ts b/src/app/fda/cross-entity-search/cross-entity-search.component.ts index 6d4b3e66d..f54f8e183 100644 --- a/src/app/fda/cross-entity-search/cross-entity-search.component.ts +++ b/src/app/fda/cross-entity-search/cross-entity-search.component.ts @@ -392,7 +392,6 @@ export class CrossEntitySearchComponent implements OnInit { this.statusMessage = "Applying " + this.subEntityDisplay + " facets and will reload " + this.thisEntityDisplay + " search results."; // ******** Perform bulk search on sub-entity after FACET SELECTION on sub-entity ******** - //@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ this.getSearchStatusResults(this.subEntityEndpoint, this.bulkSearchKey, 'key', 10); // this.performBulkSearch(this.subEntityEndpoint, this.bulkQID, 'key', this.MAX_RECORD); diff --git a/src/app/fda/product/products-browse/products-browse.component.html b/src/app/fda/product/products-browse/products-browse.component.html index fbc0b880a..450990afc 100644 --- a/src/app/fda/product/products-browse/products-browse.component.html +++ b/src/app/fda/product/products-browse/products-browse.component.html @@ -258,6 +258,12 @@ [searchValue]="searchValue" (searchPerformed)="processSubstanceSearch($event)" eventCategory="productSearch"> +
+ +
@@ -364,7 +370,6 @@
- AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
  • @@ -837,7 +842,7 @@ + -->
  • diff --git a/src/app/fda/product/products-browse/products-browse.component.ts b/src/app/fda/product/products-browse/products-browse.component.ts index ac68cfb0a..e6eecfcf5 100644 --- a/src/app/fda/product/products-browse/products-browse.component.ts +++ b/src/app/fda/product/products-browse/products-browse.component.ts @@ -32,6 +32,7 @@ import { StructureImageModalComponent, StructureService } from '@gsrs-core/struc import { JsonDialogFdaComponent } from '../../json-dialog-fda/json-dialog-fda.component'; /* GSRS Product Imports */ +import { BulkSearchService } from '@gsrs-core/bulk-search/service/bulk-search.service'; import { GeneralService } from '../../service/general.service'; import { ProductService } from '../service/product.service'; import { Product, ProductIngredient } from '../model/product.model'; @@ -138,6 +139,7 @@ export class ProductsBrowseComponent implements OnInit, AfterViewInit, OnDestroy private subscriptions: Array = []; constructor( + public bulkSearchService: BulkSearchService, public productService: ProductService, private authService: AuthService, private facetManagerService: FacetsManagerService, @@ -899,8 +901,80 @@ export class ProductsBrowseComponent implements OnInit, AfterViewInit, OnDestroy return old; } + createQueryTextWithIds(idListForSearch: Array, entity: string, rootId: boolean = false): string { + let queryText = ''; + + idListForSearch.forEach((id, index) => { + if (id) { + if (index > 0) { + queryText = queryText + '\n'; + } + if (entity && entity === 'substances') { + queryText = queryText + 'root_uuid:"' + id + '"'; + } else { + if (rootId) { + queryText = queryText + 'root_id:"' + id + '"'; + } else { + queryText = queryText + 'entity_link_substances:"' + id + '"'; + } + } + } + }); + + return queryText; + } + + getBulkQuery(substanceIdLists: Array, entity: string) { + let queryText = this.createQueryTextWithIds(substanceIdLists, entity); + + if (queryText) { + this.bulkSearchService.postOrPutBulkQuery(entity, queryText).subscribe(result => { + if (result) { + if (result.id) { + this.forwardToSubstance(result.id); + } + } + }); + } + } + + showAllSubstances() { + this.getSearchIdsOnly(true, "all substances"); + } + + forwardToSubstance(bulkQID: number) { + + let currentUrl = this.location.path(); + alert('Current URL:' + currentUrl); + + // store values in array to retreive later from localStorage + let item = { + 'allSubFromProductUrl': currentUrl + }; + + // Store current url in cookies + const searchItemHash = this.utilsService.hashCode(); + + // Store parameters in local storage + localStorage.setItem(searchItemHash.toString(), JSON.stringify(item)); + + const navigationExtras: NavigationExtras = { + queryParams: {} + }; + + if (bulkQID > 0) { + navigationExtras.queryParams['bulkQID'] = bulkQID; + } + + if (searchItemHash) { + navigationExtras.queryParams['allSubFromProd-hash'] = searchItemHash; + } + + this.router.navigate(['/browse-substance'], navigationExtras); + } + // Need this for Cross Entity Search. Return all the IDs only for the current search - getSearchIdsOnly(doPerformSearch: boolean) { + getSearchIdsOnly(doPerformSearch: boolean = true, searchType?: string) { if (doPerformSearch) { let idListsTemp: Array = []; @@ -933,8 +1007,12 @@ export class ProductsBrowseComponent implements OnInit, AfterViewInit, OnDestroy // Copy after the last record if (response.content.length == index + 1) { - // Get Search Product Ids - this.getSearchProductIds(idListsTemp); + if (searchType && searchType === 'all substances') { + this.getBulkQuery(idListsTemp, 'substances',); + } else { + // Get Search Product Ids for Cross Entity Search + this.getSearchProductIds(idListsTemp); + } // For Cross Entity Search, copy idListTemp to idList after the loop so that change detection happens only once // this.idLists = idListsTemp; diff --git a/src/app/fda/product/service/product.service.ts b/src/app/fda/product/service/product.service.ts index 6a242004a..d28f629e2 100644 --- a/src/app/fda/product/service/product.service.ts +++ b/src/app/fda/product/service/product.service.ts @@ -121,8 +121,8 @@ export class ProductService extends BaseHttpService { let params = new FacetHttpParams(); - params = params.append('top', top.toString()); params = params.append('skip', skip.toString()); + params = params.append('top', top.toString()); params = params.append('fdim', fdim.toString()); if (view) { From 7f3933ead63e78a192c35d39ba624df566dec4bc Mon Sep 17 00:00:00 2001 From: Newatia Date: Mon, 9 Jun 2025 09:29:51 -0400 Subject: [PATCH 048/408] fixed export in product --- src/app/fda/product/service/product.service.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/fda/product/service/product.service.ts b/src/app/fda/product/service/product.service.ts index 6a242004a..d28f629e2 100644 --- a/src/app/fda/product/service/product.service.ts +++ b/src/app/fda/product/service/product.service.ts @@ -121,8 +121,8 @@ export class ProductService extends BaseHttpService { let params = new FacetHttpParams(); - params = params.append('top', top.toString()); params = params.append('skip', skip.toString()); + params = params.append('top', top.toString()); params = params.append('fdim', fdim.toString()); if (view) { From 4bd6b84a09e2a267aed7c8e8be18150df4660db7 Mon Sep 17 00:00:00 2001 From: Mitch Miller Date: Tue, 1 Jul 2025 23:04:59 -0400 Subject: [PATCH 049/408] on the way to handling SMILES from a text file --- .../import-dialog.component.html | 14 ++++++- .../import-management.component.html | 6 +-- .../import-management.component.ts | 38 ++++++++++--------- src/app/fda/config/config.json | 4 ++ 4 files changed, 40 insertions(+), 22 deletions(-) diff --git a/src/app/core/admin/import-management/import-dialog/import-dialog.component.html b/src/app/core/admin/import-management/import-dialog/import-dialog.component.html index 22f659141..fb343f32d 100644 --- a/src/app/core/admin/import-management/import-dialog/import-dialog.component.html +++ b/src/app/core/admin/import-management/import-dialog/import-dialog.component.html @@ -138,7 +138,9 @@

    Molfile
    -
    +
    + +
    This value will be pulled from molefile field of your file. Please do not change without a specific reason. @@ -154,6 +156,16 @@

    +
    +
    SMILES
    +
    + + +
    +
    +

    Note
    diff --git a/src/app/core/admin/import-management/import-management.component.html b/src/app/core/admin/import-management/import-management.component.html index ad3773811..8fac9c77e 100644 --- a/src/app/core/admin/import-management/import-management.component.html +++ b/src/app/core/admin/import-management/import-management.component.html @@ -30,9 +30,9 @@ {{adapter.adapterName}}
    - -
    -
    +
    + +
    {{adapter.description}}. Supported extensions: {{ext}}{{!last? ',':''}}
    diff --git a/src/app/core/admin/import-management/import-management.component.ts b/src/app/core/admin/import-management/import-management.component.ts index 27772d2c5..e8fcdf8de 100644 --- a/src/app/core/admin/import-management/import-management.component.ts +++ b/src/app/core/admin/import-management/import-management.component.ts @@ -99,7 +99,7 @@ this.fieldList = []; openScrubber(templateRef:any, index: number): void { this.save = false; this.settingsActive = this.postResp.adapterSettings.actions[index]; - + console.log(`in openScrubber, this.settingsActive: ${this.settingsActive}`); const dialogref = this.dialog.open(ImportScrubberComponent, { @@ -124,28 +124,28 @@ openScrubber(templateRef:any, index: number): void { openAction(templateRef:any, index: number): void { this.save = false; - this.settingsActive = this.postResp.adapterSettings.actions[index]; - + this.settingsActive = this.postResp.adapterSettings.actions[index]; + console.log(`in openAction, this.settingsActive: ${JSON.stringify(this.settingsActive)}`); - const dialogref = this.dialog.open(ImportDialogComponent, { - minHeight: '500px', - width: '800px', - data: { - settingsActive: JSON.parse(JSON.stringify(this.postResp.adapterSettings.actions[index])), - fieldList: this.fieldList - } - }); - this.overlayContainer.style.zIndex = '1002'; + const dialogref = this.dialog.open(ImportDialogComponent, { + minHeight: '500px', + width: '800px', + data: { + settingsActive: JSON.parse(JSON.stringify(this.postResp.adapterSettings.actions[index])), + fieldList: this.fieldList + } + }); + this.overlayContainer.style.zIndex = '1002'; - dialogref.afterClosed().subscribe(result => { - this.overlayContainer.style.zIndex = null; + dialogref.afterClosed().subscribe(result => { + this.overlayContainer.style.zIndex = null; - if(result) { - this.postResp.adapterSettings.actions[index] = result; - } + if(result) { + this.postResp.adapterSettings.actions[index] = result; + } - }); + }); } changePreview(direction: string) { @@ -176,6 +176,7 @@ ngOnInit() { if(result) { // this.setDemo(); this.demo = result; + console.log(`in getadapters, result: ${ JSON.stringify(result)}`); } else { alert('adapters set but invalid response'); } @@ -205,6 +206,7 @@ ngOnInit() { this.demo.forEach(entry => { entry.fileExtensions.forEach(ext => { + console.log(`looking at ext ${ext}`); if (!extArr.includes(ext)) { extArr.push(ext); } diff --git a/src/app/fda/config/config.json b/src/app/fda/config/config.json index 2ef660021..8e5f40e53 100644 --- a/src/app/fda/config/config.json +++ b/src/app/fda/config/config.json @@ -24,6 +24,10 @@ "stagingArea": { "mergeAction": false }, + "apiBaseUrl": "http://localhost:8081/ginas/app/", + "gsrsHomeBaseUrl": "http://localhost:8081/ginas/app/ui/", + "occasionalApiBasePath": "/ginas/app", + "userRegistration": { "configurations": { "emailForm": { From 86eb518e51e458fb26950338edf52b9d0bd789b4 Mon Sep 17 00:00:00 2001 From: Mitch Miller Date: Mon, 7 Jul 2025 13:34:52 -0400 Subject: [PATCH 050/408] pass file delimiter when uploading a delimited text file --- .../import-dialog/import-dialog.component.ts | 6 ++++++ .../import-management.component.html | 4 ++++ .../import-management.component.ts | 16 +++++++++++++--- 3 files changed, 23 insertions(+), 3 deletions(-) diff --git a/src/app/core/admin/import-management/import-dialog/import-dialog.component.ts b/src/app/core/admin/import-management/import-dialog/import-dialog.component.ts index ff9e5f27b..77cb9f368 100644 --- a/src/app/core/admin/import-management/import-dialog/import-dialog.component.ts +++ b/src/app/core/admin/import-management/import-dialog/import-dialog.component.ts @@ -33,6 +33,9 @@ export class ImportDialogComponent implements OnInit { noteActions: any = { "note":"" }; + importStructureActions: any = { + "smiles":"" + } settingTypes = ["Create Name Action", "Create Code Action", "Create Property Action", "Create Note Action"]; constructor( public cvService: ControlledVocabularyService, @@ -65,6 +68,9 @@ export class ImportDialogComponent implements OnInit { }else if (action.value == "Create Note Action") { this.settingsActive.actionParameters = this.noteActions; this.settingsActive.actionName = 'note_import'; + } else if (action.value == "Import Structure Action") { + this.settingsActive.actionParameters = this.importStructureActions; + this.settingsActive.actionName = 'structure_and_moieties_from_text'; } this.settingsActive.label = action.value; // } diff --git a/src/app/core/admin/import-management/import-management.component.html b/src/app/core/admin/import-management/import-management.component.html index 8fac9c77e..8702f7b47 100644 --- a/src/app/core/admin/import-management/import-management.component.html +++ b/src/app/core/admin/import-management/import-management.component.html @@ -18,6 +18,10 @@
    +
    + + +
    diff --git a/src/app/core/admin/import-management/import-management.component.ts b/src/app/core/admin/import-management/import-management.component.ts index e8fcdf8de..5a39dec2b 100644 --- a/src/app/core/admin/import-management/import-management.component.ts +++ b/src/app/core/admin/import-management/import-management.component.ts @@ -22,6 +22,7 @@ demo: any; uploadForm: FormGroup; filename: string; fileType: string; +fileDelim: string; audit = false; processing = false; message: string; @@ -61,9 +62,6 @@ constructor( private dialog: MatDialog, private structureService: StructureService - - - ) { } setAdapter(event?: any) { @@ -235,6 +233,7 @@ ngOnInit() { formData.append('file', this.uploadForm.get('file').value); formData.append('file-type', this.fileType); + formData.append('lineValueDelimiter', this.fileDelim); this.adminService.postAdapterFile(formData, this.adapterKey).pipe(take(1)).subscribe(response => { console.log(response); this.loadingService.setLoading(false); @@ -345,6 +344,16 @@ onFileSelect(event): void { } } +onDelimiterChange(event):void { + if(event.target.value != null) { + this.fileDelim = event.target.value; + this.adapterSettings.lineValueDelimiter = event.target.value; + console.log(`set lineValueDelimiter to ${event.target.value} `); + } else { + console.log(`onDelimiterChange, event: ${JSON.stringify(event)}`); + } +} + openInput(): void { document.getElementById('fileInput').click(); } @@ -377,6 +386,7 @@ callPreview(): void { formData.append('file', this.uploadForm.get('file').value); formData.append('file-type', this.fileType); + formData.append('lineValueDelimiter', this.fileDelim); this.preview = []; let tosend = JSON.parse(JSON.stringify(this.postResp)); this.adminService.previewAdapter(this.fileID, tosend, this.adapterKey, this.previewLimit ).pipe(take(1)).subscribe(response => { From f952be2199602c0a4fb215b48b769009752544cd Mon Sep 17 00:00:00 2001 From: Mitch Miller Date: Mon, 7 Jul 2025 22:40:07 -0400 Subject: [PATCH 051/408] allow selection of field delimiter and quote flag. Preview first few lines of text. --- .../import-management.component.html | 35 ++++++------- .../import-management.component.ts | 51 +++++++++++++++++-- 2 files changed, 61 insertions(+), 25 deletions(-) diff --git a/src/app/core/admin/import-management/import-management.component.html b/src/app/core/admin/import-management/import-management.component.html index 8702f7b47..5692bf5cd 100644 --- a/src/app/core/admin/import-management/import-management.component.html +++ b/src/app/core/admin/import-management/import-management.component.html @@ -14,13 +14,24 @@
    {{filename? filename: 'no file chosen'}}
    -
    -
    - - +
    +    + +
    + +
    +    + +
    + +
    + +
    +                        {{firstNLines}}
    +                
    @@ -267,19 +278,3 @@

    Importing {{filename}}

    - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/app/core/admin/import-management/import-management.component.ts b/src/app/core/admin/import-management/import-management.component.ts index 5a39dec2b..611f23a83 100644 --- a/src/app/core/admin/import-management/import-management.component.ts +++ b/src/app/core/admin/import-management/import-management.component.ts @@ -22,7 +22,8 @@ demo: any; uploadForm: FormGroup; filename: string; fileType: string; -fileDelim: string; +fileDelim: string = "\t"; +removeQuotes:boolean = false; audit = false; processing = false; message: string; @@ -52,6 +53,11 @@ executeLoading = false; scrubberSchema: any; scrubberModel: any; uuidInt = 1; +showingTextFile: boolean = false; +firstNLines: string[]; +linesToPreview: number = 8; +dataPreviewSize: number = 10240; + constructor( public formBuilder: FormBuilder, public adminService: AdminService, @@ -164,17 +170,16 @@ changePreview(direction: string) { ngOnInit() { this.overlayContainer = this.overlayContainerService.getContainerElement(); - this.uploadForm = this.formBuilder.group({ file: [''], fileType: ['SDF'] }); - this.fileType = 'SDF'; + //this.fileType = 'SDF'; this.adminService.getAdapters().subscribe(result => { if(result) { // this.setDemo(); this.demo = result; - console.log(`in getadapters, result: ${ JSON.stringify(result)}`); + //console.log(`in getadapters, result: ${ JSON.stringify(result)}`); } else { alert('adapters set but invalid response'); } @@ -234,6 +239,8 @@ ngOnInit() { formData.append('file', this.uploadForm.get('file').value); formData.append('file-type', this.fileType); formData.append('lineValueDelimiter', this.fileDelim); + formData.append('removeQuotes', this.removeQuotes.toString()); + console.log(`in onSubmit, this.removeQuotes: ${this.removeQuotes}`); this.adminService.postAdapterFile(formData, this.adapterKey).pipe(take(1)).subscribe(response => { console.log(response); this.loadingService.setLoading(false); @@ -327,12 +334,20 @@ onFileSelect(event): void { let extension = file.name.split('.'); extension = extension[extension.length - 1]; + this.fileType = extension; if(this.demo) { this.demo.forEach(val => { val.fileExtensions.forEach(ext => { if (ext.toUpperCase() == extension.toUpperCase()) { this.adapterSettings = val.parameters; this.adapterKey = val.adapterKey; + console.log(`val.adapterKey: ${val.adapterKey}`); + if(val.adapterKey.toUpperCase().indexOf('TEXT') >-1) { + this.showingTextFile = true; + this.showFirstLines(file); + } else { + this.showingTextFile = false; + } } }); @@ -347,13 +362,17 @@ onFileSelect(event): void { onDelimiterChange(event):void { if(event.target.value != null) { this.fileDelim = event.target.value; - this.adapterSettings.lineValueDelimiter = event.target.value; console.log(`set lineValueDelimiter to ${event.target.value} `); } else { console.log(`onDelimiterChange, event: ${JSON.stringify(event)}`); } } +onQuotesChange(event): void { + this.removeQuotes = event.target.checked; + console.log(`setting removeQuotes to ${this.removeQuotes}`); +} + openInput(): void { document.getElementById('fileInput').click(); } @@ -387,6 +406,8 @@ callPreview(): void { formData.append('file', this.uploadForm.get('file').value); formData.append('file-type', this.fileType); formData.append('lineValueDelimiter', this.fileDelim); + formData.append('removeQuotes', this.removeQuotes.toString()); + console.log(`in callPreview, this.removeQuotes: ${this.removeQuotes}`); this.preview = []; let tosend = JSON.parse(JSON.stringify(this.postResp)); this.adminService.previewAdapter(this.fileID, tosend, this.adapterKey, this.previewLimit ).pipe(take(1)).subscribe(response => { @@ -458,5 +479,25 @@ openImageModal(preview: any): void { }); } +showFirstLines(file: File): void { + console.log('starting showFirstLines'); + //from perplexity: + const reader = new FileReader(); + + reader.onload = () => { + // Read the file content as text + const text = reader.result as string; + // Split into lines (handle both \n and \r\n) + const lines = text.split(/\r?\n/).slice(0, this.linesToPreview); // Get first 5 lines + let displayedLines = []; + for(var line of lines ) { + displayedLines.push(line+'\n'); + } + this.firstNLines = displayedLines; + }; + // Only read the first few KB for very large files + const blob = file.slice(0, this.dataPreviewSize); // 10KB should be enough for a few lines + reader.readAsText(blob); +} } From 7e97ba25405257585af416894b306425233ac736 Mon Sep 17 00:00:00 2001 From: Mitch Miller Date: Mon, 7 Jul 2025 23:09:11 -0400 Subject: [PATCH 052/408] minor tweak --- .../core/admin/import-management/import-management.component.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/app/core/admin/import-management/import-management.component.ts b/src/app/core/admin/import-management/import-management.component.ts index 611f23a83..a47fafa9e 100644 --- a/src/app/core/admin/import-management/import-management.component.ts +++ b/src/app/core/admin/import-management/import-management.component.ts @@ -492,6 +492,7 @@ showFirstLines(file: File): void { let displayedLines = []; for(var line of lines ) { displayedLines.push(line+'\n'); + console.log(`appending line ${line}`); } this.firstNLines = displayedLines; }; From a2b711f70d4aac04d95ee38771a8939afddaa4c1 Mon Sep 17 00:00:00 2001 From: Mitch Miller Date: Tue, 8 Jul 2025 01:50:16 -0400 Subject: [PATCH 053/408] cleaned up display of file data --- .../import-management.component.html | 1 - .../import-management.component.ts | 13 ++++++++----- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/app/core/admin/import-management/import-management.component.html b/src/app/core/admin/import-management/import-management.component.html index 5692bf5cd..dee6c95c1 100644 --- a/src/app/core/admin/import-management/import-management.component.html +++ b/src/app/core/admin/import-management/import-management.component.html @@ -34,7 +34,6 @@ -

    diff --git a/src/app/core/admin/import-management/import-management.component.ts b/src/app/core/admin/import-management/import-management.component.ts index a47fafa9e..634fb0076 100644 --- a/src/app/core/admin/import-management/import-management.component.ts +++ b/src/app/core/admin/import-management/import-management.component.ts @@ -54,7 +54,7 @@ scrubberSchema: any; scrubberModel: any; uuidInt = 1; showingTextFile: boolean = false; -firstNLines: string[]; +firstNLines: string; linesToPreview: number = 8; dataPreviewSize: number = 10240; @@ -489,12 +489,15 @@ showFirstLines(file: File): void { const text = reader.result as string; // Split into lines (handle both \n and \r\n) const lines = text.split(/\r?\n/).slice(0, this.linesToPreview); // Get first 5 lines + this.firstNLines = lines.join('\n'); let displayedLines = []; - for(var line of lines ) { - displayedLines.push(line+'\n'); - console.log(`appending line ${line}`); + /* for(var line of lines ) { + let cleanLine: string = line + '\n'; + displayedLines.push(cleanLine); + console.log(`appending line "${cleanLine}"`); } - this.firstNLines = displayedLines; + this.firstNLines = displayedLines;*/ + }; // Only read the first few KB for very large files const blob = file.slice(0, this.dataPreviewSize); // 10KB should be enough for a few lines From b0f904bf34243885008d73c5edacd4a9b9265f06 Mon Sep 17 00:00:00 2001 From: Newatia Date: Mon, 14 Jul 2025 11:33:05 -0400 Subject: [PATCH 054/408] added buttons in substance and ivp --- .../substances-browse.component.html | 24 +++++- .../substances-browse.component.scss | 8 ++ .../substances-browse.component.ts | 84 ++++++++++++++++++- .../applications-browse.component.html | 6 +- src/app/fda/config/config.json | 1 + ...invitro-pharmacology-browse.component.html | 16 +++- ...invitro-pharmacology-browse.component.scss | 4 + .../invitro-pharmacology-browse.component.ts | 22 +++++ 8 files changed, 155 insertions(+), 10 deletions(-) diff --git a/src/app/core/substances-browse/substances-browse.component.html b/src/app/core/substances-browse/substances-browse.component.html index 08edf14a1..4b3f106ae 100644 --- a/src/app/core/substances-browse/substances-browse.component.html +++ b/src/app/core/substances-browse/substances-browse.component.html @@ -411,7 +411,8 @@
    - + + @@ -428,7 +429,28 @@ of {{lastPage}} + +
    + +
    + +
    + + +
    + +
    +
    + + +
    diff --git a/src/app/core/substances-browse/substances-browse.component.scss b/src/app/core/substances-browse/substances-browse.component.scss index 2dc2787d1..18526d978 100644 --- a/src/app/core/substances-browse/substances-browse.component.scss +++ b/src/app/core/substances-browse/substances-browse.component.scss @@ -810,10 +810,18 @@ margin-left: 20px; margin-left: -10px; } +.marginleft20px { + margin-left: 20px; +} + .marginright10px { margin-right: 10px; } .marginbottom20px { margin-bottom: 20px; +} + +.divflex { + display: flex; } \ No newline at end of file diff --git a/src/app/core/substances-browse/substances-browse.component.ts b/src/app/core/substances-browse/substances-browse.component.ts index f5badd7db..27611f4bd 100644 --- a/src/app/core/substances-browse/substances-browse.component.ts +++ b/src/app/core/substances-browse/substances-browse.component.ts @@ -1356,10 +1356,80 @@ export class SubstancesBrowseComponent implements OnInit, AfterViewInit, OnDestr }); } + createQueryTextWithIds(idListForSearch: Array, entity: string, rootId: boolean = false): string { + let queryText = ''; + + idListForSearch.forEach((id, index) => { + if (id) { + if (index > 0) { + queryText = queryText + '\n'; + } + if (entity && entity === 'substances') { + queryText = queryText + 'root_uuid:"' + id + '"'; + } else { + if (rootId) { + queryText = queryText + 'root_id:"' + id + '"'; + } else { + queryText = queryText + 'entity_link_substances:"' + id + '"'; + } + } + } + }); + + return queryText; + } + + getBulkQuery(substanceIdLists: Array, entity: string) { + let queryText = this.createQueryTextWithIds(substanceIdLists, entity); + + if (queryText) { + this.bulkSearchService.postOrPutBulkQuery(entity, queryText).subscribe(result => { + if (result) { + if (result.id) { + this.forwardToSubstance(result.id, entity); + } + } + }); + } + } + + showAllApplications() { + this.getSearchIdsOnly(true, "all applications"); + } + + showAllProducts() { + this.getSearchIdsOnly(true, "all products"); + } + + forwardToSubstance(bulkQID: number, entity: string) { + // Store current url in cookies + + const searchItemHash = this.utilsService.hashCode(); + + // Store parameters in local storage + // localStorage.setItem(searchItemHash.toString(), JSON.stringify(item)); + + const navigationExtras: NavigationExtras = { + queryParams: {} + }; + + if (bulkQID > 0) { + navigationExtras.queryParams['bulkQID'] = bulkQID; + } + + if (entity && entity === 'applications') { + this.router.navigate(['/browse-applications'], navigationExtras); + } else if (entity && entity === 'products') { + this.router.navigate(['/browse-products'], navigationExtras); + } else { + this.router.navigate(['/browse-substance'], navigationExtras); + } + } + // Need this for Cross Entity Search. Return all the IDs only for the current search - getSearchIdsOnly(doPerformSearch: boolean) { + getSearchIdsOnly(doPerformSearch: boolean, searchType?: string) { if (doPerformSearch) { - let idListsTemp: Array = []; + let idListsTemp: Array = []; let iterations = 0; const skip = 0; @@ -1399,8 +1469,14 @@ export class SubstancesBrowseComponent implements OnInit, AfterViewInit, OnDestr } // Copy after the last record if (results.length == index + 1) { - // For Cross Entity Search, copy idListTemp to idList after the loop so that change detection happens only once - this.idLists = idListsTemp; + if (searchType && searchType === 'all applications') { + this.getBulkQuery(idListsTemp, 'applications',); + } else if (searchType && searchType === 'all products') { + this.getBulkQuery(idListsTemp, 'products',); + } else { + // For Cross Entity Search, copy idListTemp to idList after the loop so that change detection happens only once + this.idLists = idListsTemp; + } } }); // forEach } else { diff --git a/src/app/fda/application/applications-browse/applications-browse.component.html b/src/app/fda/application/applications-browse/applications-browse.component.html index 621ed6463..f743b9097 100644 --- a/src/app/fda/application/applications-browse/applications-browse.component.html +++ b/src/app/fda/application/applications-browse/applications-browse.component.html @@ -137,7 +137,8 @@
    - @@ -150,7 +151,8 @@
    -
    +
    diff --git a/src/app/fda/config/config.json b/src/app/fda/config/config.json index 2ef660021..9567b3035 100644 --- a/src/app/fda/config/config.json +++ b/src/app/fda/config/config.json @@ -15,6 +15,7 @@ "useDataUrl": false, "showCrossEntitySearchDropdown": true, "restApiPrefix": "/ginas/app", + "disableJSDraw": true, "authenticateAs": { "apiUsername": null, "apiPassword": null, diff --git a/src/app/fda/invitro-pharmacology/invitro-pharmacology-browse/invitro-pharmacology-browse.component.html b/src/app/fda/invitro-pharmacology/invitro-pharmacology-browse/invitro-pharmacology-browse.component.html index 9044ac773..e972cdc5c 100644 --- a/src/app/fda/invitro-pharmacology/invitro-pharmacology-browse/invitro-pharmacology-browse.component.html +++ b/src/app/fda/invitro-pharmacology/invitro-pharmacology-browse/invitro-pharmacology-browse.component.html @@ -272,12 +272,12 @@ -
    + +
    + + + + article +   + +
    + -
    +
    { + }); + this.subscriptions.push(dialogSubscription); + } + saveJSON(id: number): void { let json = this.assays[id]; const uri = this.sanitizer.bypassSecurityTrustUrl('data:text/json;charset=UTF-8,' + encodeURIComponent(JSON.stringify(json))); From 33961f8c5c762308d485d907f255b5d5b2c2e2ac Mon Sep 17 00:00:00 2001 From: alx652 Date: Mon, 14 Jul 2025 14:02:21 -0400 Subject: [PATCH 055/408] update license code, between older commit and tag --- src/app/core/assets/jsdraw/Scilligence.JSDraw2.Pro.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/app/core/assets/jsdraw/Scilligence.JSDraw2.Pro.js b/src/app/core/assets/jsdraw/Scilligence.JSDraw2.Pro.js index b007bf5b5..d4e0b1148 100644 --- a/src/app/core/assets/jsdraw/Scilligence.JSDraw2.Pro.js +++ b/src/app/core/assets/jsdraw/Scilligence.JSDraw2.Pro.js @@ -25,9 +25,8 @@ JSDraw2.password = { encrypt: true, key: null, iv: null }; // Place the license code below // Licensed to: FDA // Product: JSDraw -// Expiration Date: 2025-Jul-30 -JSDraw2.licensecode='405562538916781761723242424242424131213141512181'; - +// Expiration Date: 2026-Jul-30 +JSDraw2.licensecode='405562537916781761723242424242424131213141512181'; ////////////////////////////////////////////////////////////////////////////////// // JSDraw default settings From 17e966f5e278190dd775545f21c6c5d6b3cb4997 Mon Sep 17 00:00:00 2001 From: Mitch Miller Date: Tue, 1 Jul 2025 23:04:59 -0400 Subject: [PATCH 056/408] on the way to handling SMILES from a text file --- .../import-dialog.component.html | 14 ++++++- .../import-management.component.html | 6 +-- .../import-management.component.ts | 38 ++++++++++--------- src/app/fda/config/config.json | 4 ++ 4 files changed, 40 insertions(+), 22 deletions(-) diff --git a/src/app/core/admin/import-management/import-dialog/import-dialog.component.html b/src/app/core/admin/import-management/import-dialog/import-dialog.component.html index 22f659141..fb343f32d 100644 --- a/src/app/core/admin/import-management/import-dialog/import-dialog.component.html +++ b/src/app/core/admin/import-management/import-dialog/import-dialog.component.html @@ -138,7 +138,9 @@

    Molfile
    -
    +
    + +
    This value will be pulled from molefile field of your file. Please do not change without a specific reason. @@ -154,6 +156,16 @@

    +
    +
    SMILES
    +
    + + +
    +
    +

    Note
    diff --git a/src/app/core/admin/import-management/import-management.component.html b/src/app/core/admin/import-management/import-management.component.html index ad3773811..8fac9c77e 100644 --- a/src/app/core/admin/import-management/import-management.component.html +++ b/src/app/core/admin/import-management/import-management.component.html @@ -30,9 +30,9 @@ {{adapter.adapterName}}
    - -
    -
    +
    + +
    {{adapter.description}}. Supported extensions: {{ext}}{{!last? ',':''}}
    diff --git a/src/app/core/admin/import-management/import-management.component.ts b/src/app/core/admin/import-management/import-management.component.ts index 27772d2c5..e8fcdf8de 100644 --- a/src/app/core/admin/import-management/import-management.component.ts +++ b/src/app/core/admin/import-management/import-management.component.ts @@ -99,7 +99,7 @@ this.fieldList = []; openScrubber(templateRef:any, index: number): void { this.save = false; this.settingsActive = this.postResp.adapterSettings.actions[index]; - + console.log(`in openScrubber, this.settingsActive: ${this.settingsActive}`); const dialogref = this.dialog.open(ImportScrubberComponent, { @@ -124,28 +124,28 @@ openScrubber(templateRef:any, index: number): void { openAction(templateRef:any, index: number): void { this.save = false; - this.settingsActive = this.postResp.adapterSettings.actions[index]; - + this.settingsActive = this.postResp.adapterSettings.actions[index]; + console.log(`in openAction, this.settingsActive: ${JSON.stringify(this.settingsActive)}`); - const dialogref = this.dialog.open(ImportDialogComponent, { - minHeight: '500px', - width: '800px', - data: { - settingsActive: JSON.parse(JSON.stringify(this.postResp.adapterSettings.actions[index])), - fieldList: this.fieldList - } - }); - this.overlayContainer.style.zIndex = '1002'; + const dialogref = this.dialog.open(ImportDialogComponent, { + minHeight: '500px', + width: '800px', + data: { + settingsActive: JSON.parse(JSON.stringify(this.postResp.adapterSettings.actions[index])), + fieldList: this.fieldList + } + }); + this.overlayContainer.style.zIndex = '1002'; - dialogref.afterClosed().subscribe(result => { - this.overlayContainer.style.zIndex = null; + dialogref.afterClosed().subscribe(result => { + this.overlayContainer.style.zIndex = null; - if(result) { - this.postResp.adapterSettings.actions[index] = result; - } + if(result) { + this.postResp.adapterSettings.actions[index] = result; + } - }); + }); } changePreview(direction: string) { @@ -176,6 +176,7 @@ ngOnInit() { if(result) { // this.setDemo(); this.demo = result; + console.log(`in getadapters, result: ${ JSON.stringify(result)}`); } else { alert('adapters set but invalid response'); } @@ -205,6 +206,7 @@ ngOnInit() { this.demo.forEach(entry => { entry.fileExtensions.forEach(ext => { + console.log(`looking at ext ${ext}`); if (!extArr.includes(ext)) { extArr.push(ext); } diff --git a/src/app/fda/config/config.json b/src/app/fda/config/config.json index 2ef660021..8e5f40e53 100644 --- a/src/app/fda/config/config.json +++ b/src/app/fda/config/config.json @@ -24,6 +24,10 @@ "stagingArea": { "mergeAction": false }, + "apiBaseUrl": "http://localhost:8081/ginas/app/", + "gsrsHomeBaseUrl": "http://localhost:8081/ginas/app/ui/", + "occasionalApiBasePath": "/ginas/app", + "userRegistration": { "configurations": { "emailForm": { From c9cd75e5055eb44f186180a10650e34c91a0a747 Mon Sep 17 00:00:00 2001 From: Mitch Miller Date: Mon, 7 Jul 2025 13:34:52 -0400 Subject: [PATCH 057/408] pass file delimiter when uploading a delimited text file --- .../import-dialog/import-dialog.component.ts | 6 ++++++ .../import-management.component.html | 4 ++++ .../import-management.component.ts | 16 +++++++++++++--- 3 files changed, 23 insertions(+), 3 deletions(-) diff --git a/src/app/core/admin/import-management/import-dialog/import-dialog.component.ts b/src/app/core/admin/import-management/import-dialog/import-dialog.component.ts index ff9e5f27b..77cb9f368 100644 --- a/src/app/core/admin/import-management/import-dialog/import-dialog.component.ts +++ b/src/app/core/admin/import-management/import-dialog/import-dialog.component.ts @@ -33,6 +33,9 @@ export class ImportDialogComponent implements OnInit { noteActions: any = { "note":"" }; + importStructureActions: any = { + "smiles":"" + } settingTypes = ["Create Name Action", "Create Code Action", "Create Property Action", "Create Note Action"]; constructor( public cvService: ControlledVocabularyService, @@ -65,6 +68,9 @@ export class ImportDialogComponent implements OnInit { }else if (action.value == "Create Note Action") { this.settingsActive.actionParameters = this.noteActions; this.settingsActive.actionName = 'note_import'; + } else if (action.value == "Import Structure Action") { + this.settingsActive.actionParameters = this.importStructureActions; + this.settingsActive.actionName = 'structure_and_moieties_from_text'; } this.settingsActive.label = action.value; // } diff --git a/src/app/core/admin/import-management/import-management.component.html b/src/app/core/admin/import-management/import-management.component.html index 8fac9c77e..8702f7b47 100644 --- a/src/app/core/admin/import-management/import-management.component.html +++ b/src/app/core/admin/import-management/import-management.component.html @@ -18,6 +18,10 @@
    +
    + + +
    diff --git a/src/app/core/admin/import-management/import-management.component.ts b/src/app/core/admin/import-management/import-management.component.ts index e8fcdf8de..5a39dec2b 100644 --- a/src/app/core/admin/import-management/import-management.component.ts +++ b/src/app/core/admin/import-management/import-management.component.ts @@ -22,6 +22,7 @@ demo: any; uploadForm: FormGroup; filename: string; fileType: string; +fileDelim: string; audit = false; processing = false; message: string; @@ -61,9 +62,6 @@ constructor( private dialog: MatDialog, private structureService: StructureService - - - ) { } setAdapter(event?: any) { @@ -235,6 +233,7 @@ ngOnInit() { formData.append('file', this.uploadForm.get('file').value); formData.append('file-type', this.fileType); + formData.append('lineValueDelimiter', this.fileDelim); this.adminService.postAdapterFile(formData, this.adapterKey).pipe(take(1)).subscribe(response => { console.log(response); this.loadingService.setLoading(false); @@ -345,6 +344,16 @@ onFileSelect(event): void { } } +onDelimiterChange(event):void { + if(event.target.value != null) { + this.fileDelim = event.target.value; + this.adapterSettings.lineValueDelimiter = event.target.value; + console.log(`set lineValueDelimiter to ${event.target.value} `); + } else { + console.log(`onDelimiterChange, event: ${JSON.stringify(event)}`); + } +} + openInput(): void { document.getElementById('fileInput').click(); } @@ -377,6 +386,7 @@ callPreview(): void { formData.append('file', this.uploadForm.get('file').value); formData.append('file-type', this.fileType); + formData.append('lineValueDelimiter', this.fileDelim); this.preview = []; let tosend = JSON.parse(JSON.stringify(this.postResp)); this.adminService.previewAdapter(this.fileID, tosend, this.adapterKey, this.previewLimit ).pipe(take(1)).subscribe(response => { From 494fc1e25783e23e3d94d68ebb3a65a12d1967c6 Mon Sep 17 00:00:00 2001 From: Mitch Miller Date: Mon, 7 Jul 2025 22:40:07 -0400 Subject: [PATCH 058/408] allow selection of field delimiter and quote flag. Preview first few lines of text. --- .../import-management.component.html | 35 ++++++------- .../import-management.component.ts | 51 +++++++++++++++++-- 2 files changed, 61 insertions(+), 25 deletions(-) diff --git a/src/app/core/admin/import-management/import-management.component.html b/src/app/core/admin/import-management/import-management.component.html index 8702f7b47..5692bf5cd 100644 --- a/src/app/core/admin/import-management/import-management.component.html +++ b/src/app/core/admin/import-management/import-management.component.html @@ -14,13 +14,24 @@
    {{filename? filename: 'no file chosen'}}
    -
    -
    - - +
    +    + +
    + +
    +    + +
    + +
    + +
    +                        {{firstNLines}}
    +                
    @@ -267,19 +278,3 @@

    Importing {{filename}}

    - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/app/core/admin/import-management/import-management.component.ts b/src/app/core/admin/import-management/import-management.component.ts index 5a39dec2b..611f23a83 100644 --- a/src/app/core/admin/import-management/import-management.component.ts +++ b/src/app/core/admin/import-management/import-management.component.ts @@ -22,7 +22,8 @@ demo: any; uploadForm: FormGroup; filename: string; fileType: string; -fileDelim: string; +fileDelim: string = "\t"; +removeQuotes:boolean = false; audit = false; processing = false; message: string; @@ -52,6 +53,11 @@ executeLoading = false; scrubberSchema: any; scrubberModel: any; uuidInt = 1; +showingTextFile: boolean = false; +firstNLines: string[]; +linesToPreview: number = 8; +dataPreviewSize: number = 10240; + constructor( public formBuilder: FormBuilder, public adminService: AdminService, @@ -164,17 +170,16 @@ changePreview(direction: string) { ngOnInit() { this.overlayContainer = this.overlayContainerService.getContainerElement(); - this.uploadForm = this.formBuilder.group({ file: [''], fileType: ['SDF'] }); - this.fileType = 'SDF'; + //this.fileType = 'SDF'; this.adminService.getAdapters().subscribe(result => { if(result) { // this.setDemo(); this.demo = result; - console.log(`in getadapters, result: ${ JSON.stringify(result)}`); + //console.log(`in getadapters, result: ${ JSON.stringify(result)}`); } else { alert('adapters set but invalid response'); } @@ -234,6 +239,8 @@ ngOnInit() { formData.append('file', this.uploadForm.get('file').value); formData.append('file-type', this.fileType); formData.append('lineValueDelimiter', this.fileDelim); + formData.append('removeQuotes', this.removeQuotes.toString()); + console.log(`in onSubmit, this.removeQuotes: ${this.removeQuotes}`); this.adminService.postAdapterFile(formData, this.adapterKey).pipe(take(1)).subscribe(response => { console.log(response); this.loadingService.setLoading(false); @@ -327,12 +334,20 @@ onFileSelect(event): void { let extension = file.name.split('.'); extension = extension[extension.length - 1]; + this.fileType = extension; if(this.demo) { this.demo.forEach(val => { val.fileExtensions.forEach(ext => { if (ext.toUpperCase() == extension.toUpperCase()) { this.adapterSettings = val.parameters; this.adapterKey = val.adapterKey; + console.log(`val.adapterKey: ${val.adapterKey}`); + if(val.adapterKey.toUpperCase().indexOf('TEXT') >-1) { + this.showingTextFile = true; + this.showFirstLines(file); + } else { + this.showingTextFile = false; + } } }); @@ -347,13 +362,17 @@ onFileSelect(event): void { onDelimiterChange(event):void { if(event.target.value != null) { this.fileDelim = event.target.value; - this.adapterSettings.lineValueDelimiter = event.target.value; console.log(`set lineValueDelimiter to ${event.target.value} `); } else { console.log(`onDelimiterChange, event: ${JSON.stringify(event)}`); } } +onQuotesChange(event): void { + this.removeQuotes = event.target.checked; + console.log(`setting removeQuotes to ${this.removeQuotes}`); +} + openInput(): void { document.getElementById('fileInput').click(); } @@ -387,6 +406,8 @@ callPreview(): void { formData.append('file', this.uploadForm.get('file').value); formData.append('file-type', this.fileType); formData.append('lineValueDelimiter', this.fileDelim); + formData.append('removeQuotes', this.removeQuotes.toString()); + console.log(`in callPreview, this.removeQuotes: ${this.removeQuotes}`); this.preview = []; let tosend = JSON.parse(JSON.stringify(this.postResp)); this.adminService.previewAdapter(this.fileID, tosend, this.adapterKey, this.previewLimit ).pipe(take(1)).subscribe(response => { @@ -458,5 +479,25 @@ openImageModal(preview: any): void { }); } +showFirstLines(file: File): void { + console.log('starting showFirstLines'); + //from perplexity: + const reader = new FileReader(); + + reader.onload = () => { + // Read the file content as text + const text = reader.result as string; + // Split into lines (handle both \n and \r\n) + const lines = text.split(/\r?\n/).slice(0, this.linesToPreview); // Get first 5 lines + let displayedLines = []; + for(var line of lines ) { + displayedLines.push(line+'\n'); + } + this.firstNLines = displayedLines; + }; + // Only read the first few KB for very large files + const blob = file.slice(0, this.dataPreviewSize); // 10KB should be enough for a few lines + reader.readAsText(blob); +} } From fd03fb1d019fc7ebe8856c05c892bb7f972488ab Mon Sep 17 00:00:00 2001 From: Mitch Miller Date: Mon, 7 Jul 2025 23:09:11 -0400 Subject: [PATCH 059/408] minor tweak --- .../core/admin/import-management/import-management.component.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/app/core/admin/import-management/import-management.component.ts b/src/app/core/admin/import-management/import-management.component.ts index 611f23a83..a47fafa9e 100644 --- a/src/app/core/admin/import-management/import-management.component.ts +++ b/src/app/core/admin/import-management/import-management.component.ts @@ -492,6 +492,7 @@ showFirstLines(file: File): void { let displayedLines = []; for(var line of lines ) { displayedLines.push(line+'\n'); + console.log(`appending line ${line}`); } this.firstNLines = displayedLines; }; From 2da1118dd916601eb4ae7b7fe8eded4610aca49b Mon Sep 17 00:00:00 2001 From: Mitch Miller Date: Tue, 8 Jul 2025 01:50:16 -0400 Subject: [PATCH 060/408] cleaned up display of file data --- .../import-management.component.html | 1 - .../import-management.component.ts | 13 ++++++++----- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/app/core/admin/import-management/import-management.component.html b/src/app/core/admin/import-management/import-management.component.html index 5692bf5cd..dee6c95c1 100644 --- a/src/app/core/admin/import-management/import-management.component.html +++ b/src/app/core/admin/import-management/import-management.component.html @@ -34,7 +34,6 @@
    -

    diff --git a/src/app/core/admin/import-management/import-management.component.ts b/src/app/core/admin/import-management/import-management.component.ts index a47fafa9e..634fb0076 100644 --- a/src/app/core/admin/import-management/import-management.component.ts +++ b/src/app/core/admin/import-management/import-management.component.ts @@ -54,7 +54,7 @@ scrubberSchema: any; scrubberModel: any; uuidInt = 1; showingTextFile: boolean = false; -firstNLines: string[]; +firstNLines: string; linesToPreview: number = 8; dataPreviewSize: number = 10240; @@ -489,12 +489,15 @@ showFirstLines(file: File): void { const text = reader.result as string; // Split into lines (handle both \n and \r\n) const lines = text.split(/\r?\n/).slice(0, this.linesToPreview); // Get first 5 lines + this.firstNLines = lines.join('\n'); let displayedLines = []; - for(var line of lines ) { - displayedLines.push(line+'\n'); - console.log(`appending line ${line}`); + /* for(var line of lines ) { + let cleanLine: string = line + '\n'; + displayedLines.push(cleanLine); + console.log(`appending line "${cleanLine}"`); } - this.firstNLines = displayedLines; + this.firstNLines = displayedLines;*/ + }; // Only read the first few KB for very large files const blob = file.slice(0, this.dataPreviewSize); // 10KB should be enough for a few lines From 7f29599fbec70bd6cfaaee067541cf02675cb607 Mon Sep 17 00:00:00 2001 From: alx652 Date: Mon, 14 Jul 2025 14:02:21 -0400 Subject: [PATCH 061/408] update license code, between older commit and tag --- src/app/core/assets/jsdraw/Scilligence.JSDraw2.Pro.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/app/core/assets/jsdraw/Scilligence.JSDraw2.Pro.js b/src/app/core/assets/jsdraw/Scilligence.JSDraw2.Pro.js index b007bf5b5..d4e0b1148 100644 --- a/src/app/core/assets/jsdraw/Scilligence.JSDraw2.Pro.js +++ b/src/app/core/assets/jsdraw/Scilligence.JSDraw2.Pro.js @@ -25,9 +25,8 @@ JSDraw2.password = { encrypt: true, key: null, iv: null }; // Place the license code below // Licensed to: FDA // Product: JSDraw -// Expiration Date: 2025-Jul-30 -JSDraw2.licensecode='405562538916781761723242424242424131213141512181'; - +// Expiration Date: 2026-Jul-30 +JSDraw2.licensecode='405562537916781761723242424242424131213141512181'; ////////////////////////////////////////////////////////////////////////////////// // JSDraw default settings From 75478dd3a544159ebf705ea33ef8df3d0c59f872 Mon Sep 17 00:00:00 2001 From: Newatia Date: Thu, 31 Jul 2025 14:46:05 -0400 Subject: [PATCH 062/408] updated Application --- src/app/core/config/config.model.ts | 1 + .../application-form.component.ts | 80 +++++++++---------- src/app/fda/config/config.json | 1 + 3 files changed, 38 insertions(+), 44 deletions(-) diff --git a/src/app/core/config/config.model.ts b/src/app/core/config/config.model.ts index aaacdeca7..7913283d6 100644 --- a/src/app/core/config/config.model.ts +++ b/src/app/core/config/config.model.ts @@ -94,6 +94,7 @@ export interface Config { showCrossEntitySearchDropdown?: boolean; restApiPrefix?: string; nitrosamineDisplay?: boolean; + registerApplicationCenterNotAllowed?: Array; } export interface StagingAreaSettings { diff --git a/src/app/fda/application/application-form/application-form.component.ts b/src/app/fda/application/application-form/application-form.component.ts index ca74f7889..d45dcea8d 100644 --- a/src/app/fda/application/application-form/application-form.component.ts +++ b/src/app/fda/application/application-form/application-form.component.ts @@ -1,52 +1,43 @@ import { Component, OnInit, AfterViewInit, OnDestroy, ViewEncapsulation } from '@angular/core'; -import { ApplicationService } from '../service/application.service'; + import { ActivatedRoute, Router } from '@angular/router'; -import { LoadingService } from '@gsrs-core/loading'; -import { MainNotificationService } from '@gsrs-core/main-notification'; -import { AppNotification, NotificationType } from '@gsrs-core/main-notification'; -import { GoogleAnalyticsService } from '@gsrs-core/google-analytics'; -import { UtilsService } from '@gsrs-core/utils/utils.service'; -import { AuthService } from '@gsrs-core/auth/auth.service'; -import { ControlledVocabularyService } from '../../../core/controlled-vocabulary/controlled-vocabulary.service'; -import { VocabularyTerm } from '../../../core/controlled-vocabulary/vocabulary.model'; -import { Application, ValidationMessage } from '../model/application.model'; +import { DatePipe } from '@angular/common'; +import { FormBuilder } from '@angular/forms'; +import { FormControl, FormGroup, Validators } from '@angular/forms'; +import { MatDatepickerInputEvent } from '@angular/material/datepicker'; import { Subscription } from 'rxjs'; -import * as moment from 'moment'; import { Title } from '@angular/platform-browser'; import { take } from 'rxjs/operators'; import { MatDialog } from '@angular/material/dialog'; import { OverlayContainer } from '@angular/cdk/overlay'; +import * as moment from 'moment'; + +/* GSRS Core Imports */ +import { ConfigService } from '@gsrs-core/config'; +import { LoadingService } from '@gsrs-core/loading'; +import { MainNotificationService } from '@gsrs-core/main-notification'; +import { UtilsService } from '@gsrs-core/utils/utils.service'; +import { AuthService } from '@gsrs-core/auth/auth.service'; +import { ControlledVocabularyService } from '../../../core/controlled-vocabulary/controlled-vocabulary.service'; +import { GoogleAnalyticsService } from '@gsrs-core/google-analytics'; +import { AppNotification, NotificationType } from '@gsrs-core/main-notification'; import { JsonDialogFdaComponent } from '../../json-dialog-fda/json-dialog-fda.component'; import { ConfirmDialogComponent } from '../../confirm-dialog/confirm-dialog.component'; -import { finalize } from 'rxjs/operators'; -import { CvInputComponent } from '@gsrs-core/substance-form/cv-input/cv-input.component'; -import { anyExistsFilter } from '@gsrs-core/substance-details'; -import { DatePipe } from '@angular/common'; -import { MatDatepickerInputEvent } from '@angular/material/datepicker'; -import { FormBuilder } from '@angular/forms'; -import { FormControl, FormGroup, Validators } from '@angular/forms'; -import { NativeDateAdapter, DateAdapter, MAT_NATIVE_DATE_FORMATS } from '@angular/material/core'; -import { element } from 'protractor'; + +/* GSRS Application Imports */ +import { ApplicationService } from '../service/application.service'; import { GeneralService } from '../../service/general.service'; +import { Application, ValidationMessage } from '../model/application.model'; @Component({ selector: 'app-application-form', templateUrl: './application-form.component.html', styleUrls: ['./application-form.component.scss'], - // encapsulation: ViewEncapsulation.None }) export class ApplicationFormComponent implements OnInit, AfterViewInit, OnDestroy { application: Application; - /* - centerList: Array = []; - appTypeList: Array = []; - appStatusList: Array = []; - publicDomainList: Array = []; - appSubTypeList: Array = []; -*/ - id?: number; isLoading = true; showSubmissionMessages = false; @@ -64,27 +55,23 @@ export class ApplicationFormComponent implements OnInit, AfterViewInit, OnDestro statusDateMessage = ''; appForm: FormGroup; isAdmin = false; + regAppCenterNotAllowedConfig: Array; constructor( - private applicationService: ApplicationService, - private generalService: GeneralService, + private activatedRoute: ActivatedRoute, + private router: Router, + private titleService: Title, + private overlayContainerService: OverlayContainer, + private configService: ConfigService, private authService: AuthService, private loadingService: LoadingService, private mainNotificationService: MainNotificationService, private gaService: GoogleAnalyticsService, - private utilsService: UtilsService, - private cvService: ControlledVocabularyService, - private activatedRoute: ActivatedRoute, - private router: Router, - private overlayContainerService: OverlayContainer, - private dialog: MatDialog, - private fb: FormBuilder, - private titleService: Title) { } - - // get submitDateControl() { return this.appForm.get('submitDateControl'); } + private applicationService: ApplicationService, + private generalService: GeneralService, + private dialog: MatDialog) { } ngOnInit() { - // this.generateFormContorls(); this.isAdmin = this.authService.hasRoles('admin'); this.loadingService.setLoading(true); this.overlayContainer = this.overlayContainerService.getContainerElement(); @@ -121,12 +108,17 @@ export class ApplicationFormComponent implements OnInit, AfterViewInit, OnDestro } ngOnDestroy(): void { - // this.applicationService.unloadSubstance(); this.subscriptions.forEach(subscription => { subscription.unsubscribe(); }); } + getConfiguration() { + // get config value for 'registerApplicationCenterNotAllowed'. DO NOT display the centers in the config in Register Application form + this.regAppCenterNotAllowedConfig = this.configService.configData.registerApplicationCenterNotAllowed || null; + + } + getApplicationDetails(newType?: string): void { this.applicationService.getApplicationById(this.id).subscribe(response => { if (response) { @@ -216,7 +208,7 @@ export class ApplicationFormComponent implements OnInit, AfterViewInit, OnDestro // if registering a new record if (!this.id) { if (this.application.center === 'CDER' || this.application.center === 'CBER') { - this.setValidationMessage(this.application.center + ' center is not allowed to register a new record'); + this.setValidationMessage('Application registration not allowed for ' + this.application.center); } } } diff --git a/src/app/fda/config/config.json b/src/app/fda/config/config.json index 9567b3035..3d12004b7 100644 --- a/src/app/fda/config/config.json +++ b/src/app/fda/config/config.json @@ -16,6 +16,7 @@ "showCrossEntitySearchDropdown": true, "restApiPrefix": "/ginas/app", "disableJSDraw": true, + "registerApplicationCenterNotAllowed": ["CDER", "CBER"], "authenticateAs": { "apiUsername": null, "apiPassword": null, From 0eb465245db9263c58b496a9d3b71d9046da08f9 Mon Sep 17 00:00:00 2001 From: Newatia Date: Fri, 1 Aug 2025 15:55:27 -0400 Subject: [PATCH 063/408] fixed ketcher issues --- .../structure-editor.component.html | 55 +- .../structure-editor.component.scss | 8 + .../structure-editor.component.ts | 530 +++++++++++------- src/app/core/structure/structure.service.ts | 19 +- ...bstance-form-structure-card.component.html | 5 +- ...bstance-form-structure-card.component.scss | 2 +- ...substance-form-structure-card.component.ts | 1 + src/app/fda/config/config.json | 1 + 8 files changed, 392 insertions(+), 229 deletions(-) diff --git a/src/app/core/structure-editor/structure-editor.component.html b/src/app/core/structure-editor/structure-editor.component.html index 692d04548..2e1704552 100644 --- a/src/app/core/structure-editor/structure-editor.component.html +++ b/src/app/core/structure-editor/structure-editor.component.html @@ -12,42 +12,47 @@
    -
    +
    - -
    - Load an image by pasting a copied image into the canvas with ctrl + v, or dragging a local image file -
    - + +
    +
    + Use copy dropdown menu on Ketcher editor and NOT ctrl + c to copy structure. +
    +
    + Load an image by pasting a copied image into the canvas with ctrl + v, or dragging a local image file. +
    +
    +
    -
    +
    + + Clean structure + +
    -
    -
    +
    +
    {{canvasMessage}}
    - + +
    -
    - - - - \ No newline at end of file + + + + \ No newline at end of file diff --git a/src/app/core/structure-editor/structure-editor.component.scss b/src/app/core/structure-editor/structure-editor.component.scss index 9216e8c56..8747591dd 100644 --- a/src/app/core/structure-editor/structure-editor.component.scss +++ b/src/app/core/structure-editor/structure-editor.component.scss @@ -61,3 +61,11 @@ z-index: 9988 !important; } + +.marginright15px { + margin-left: 15px; +} + +.textalignleft { + text-align: left; +} \ No newline at end of file diff --git a/src/app/core/structure-editor/structure-editor.component.ts b/src/app/core/structure-editor/structure-editor.component.ts index 98e10d947..34ea7c064 100644 --- a/src/app/core/structure-editor/structure-editor.component.ts +++ b/src/app/core/structure-editor/structure-editor.component.ts @@ -51,6 +51,8 @@ export class StructureEditorComponent implements OnInit, AfterViewInit, OnDestro enableJSDraw = true; enableKetcher = true; ketcherWindowActive = false; + firstload = true; + calledFromComponent: string; private overlayContainer: HTMLElement; @ViewChild('structure_canvas', { static: false }) myCanvas: ElementRef; @@ -68,7 +70,6 @@ export class StructureEditorComponent implements OnInit, AfterViewInit, OnDestro `${environment.baseHref || ''}assets/ketcher/static/js/main.963f80c2.js`, `${environment.baseHref || ''}assets/ketcher/static/js/583.7fb8b79c.chunk.js`, ]; - firstload = true; constructor( @Inject(PLATFORM_ID) private platformId: Object, @@ -84,18 +85,37 @@ export class StructureEditorComponent implements OnInit, AfterViewInit, OnDestro window.removeEventListener('dragover', this.preventDrag); window.removeEventListener('paste', this.checkPaste); (this.myCanvas.nativeElement).removeEventListener('click', this.listener); + + this.destroyExistingKetcherInstance(); + + this.structureService.updateReloadKetcher(true); + } + + destroyExistingKetcherInstance(): boolean { + // Delete existing Ketcher instance delete this.ketcher; + delete window['ketcher']; + let parentElement = document.getElementById('ketcherwrapper'); let childElement = document.getElementById('root'); - delete window['ketcher']; + // Check if both elements exist if (parentElement && childElement) { + // Check if parentElement has any child notes, then remove + if (parentElement.childNodes.length > 0) { // Remove the child element from the parent element parentElement.removeChild(childElement); + } - } else { + // IMPORTANT: NEED TO DELETE
    if it exists + // Otherwise the Ketcher will not launch in the next Editor + if (childElement) { + childElement.remove(); + } } + return true; + } ngAfterViewInit(): void { @@ -103,56 +123,60 @@ export class StructureEditorComponent implements OnInit, AfterViewInit, OnDestro this.canvasCopy = this.myCanvas.nativeElement; const test = (this.myCanvas.nativeElement); if (test) { - // test.addEventListener('click', this.click); + // test.addEventListener('click', this.click); } } - @Input() setMolecule(structure: any){ - if(this.structureEditor==="ketcher") { + @Input() setMolecule(structure: any) { + if (this.structureEditor === "ketcher") { this.structureService.interpretStructure(structure).subscribe(resp => { this.ketcher.setMolecule(resp.structure.molfile); }); } else { this.editor.setMolecule(structure); } - } + } - listener = () => { - var elmR=document.getElementById("root"); - if(this.structureEditor==="ketcher"){ - if( elmR && elmR.querySelector(":focus-within")){ + @Input() + set calledFrom(calledFromComp: any) { + this.calledFromComponent = calledFromComp; + } + + listener = () => { + var elmR = document.getElementById("root"); + if (this.structureEditor === "ketcher") { + if (elmR && elmR.querySelector(":focus-within")) { this.ketcherWindowActive = true; - if(this.enableJSDraw) { - this.getSketcher().activated=true; + if (this.enableJSDraw) { + this.getSketcher().activated = true; } - }else{ + } else { this.ketcherWindowActive = false; - if(this.enableJSDraw) { - this.getSketcher().activated=false; + if (this.enableJSDraw) { + this.getSketcher().activated = false; } } } } private preventDrag = (event: DragEvent) => { - // console.log('prevent drag'); + // console.log('prevent drag'); event.preventDefault(); } -// override JSDraw for Molvec paste event. Using the JSDraw menu copy function seems to ignore this at first - checkPaste = (event: ClipboardEvent ) => { - if ((this.jsdraw || this.ketcher )&& (this.ketcherWindowActive || (this.enableJSDraw && this.getSketcher().activated))) { - event.preventDefault(); - event.stopPropagation(); - event.stopImmediatePropagation(); + // override JSDraw for Molvec paste event. Using the JSDraw menu copy function seems to ignore this at first + checkPaste = (event: ClipboardEvent) => { + if ((this.jsdraw || this.ketcher) && (this.ketcherWindowActive || (this.enableJSDraw && this.getSketcher().activated))) { + event.preventDefault(); + event.stopPropagation(); + event.stopImmediatePropagation(); this.catchPaste(event); } } - -// when a dialog is opened / z-index changes occur, the editor loses it's reference this resets it when it's focused -// there is probably a better way of doing this by applying something to all dialog close events. you must first set it to 0 to take - click = (event: Event ) => { + // when a dialog is opened / z-index changes occur, the editor loses it's reference this resets it when it's focused + // there is probably a better way of doing this by applying something to all dialog close events. you must first set it to 0 to take + click = (event: Event) => { this.tempClass = 'high'; setTimeout(() => { this.tempClass = 'higher'; @@ -160,7 +184,23 @@ export class StructureEditorComponent implements OnInit, AfterViewInit, OnDestro } ngOnInit() { - window.addEventListener('click',this.listener); + this.structureService.reloadKetcher$.subscribe((reloadKetcher) => { + if (reloadKetcher === true) { + if (this.calledFromComponent && this.calledFromComponent === 'registerSubstance') { + console.log(this.calledFromComponent + ' reload ketcher:', reloadKetcher); + this.createDivRootElement(); + } + } + }); + + // Ketcher can have only one instance for the entire GSRS website. We can not have two Ketcher editor instances on same page. + // We have to destroy the existing Ketcher which is already open, then create a new instance Ketcher. + // if global variable window['ketcher'] is not null, destory it first before launching a new Ketcher editor + if (window['ketcher']) { + this.destroyExistingKetcherInstance(); + } + + window.addEventListener('click', this.listener); this.overlayContainer = this.overlayContainerService.getContainerElement(); if (isPlatformBrowser(this.platformId)) { @@ -170,6 +210,7 @@ export class StructureEditorComponent implements OnInit, AfterViewInit, OnDestro this.structureEditor = environment.structureEditor; let pref = sessionStorage.getItem('gsrsStructureEditor'); + // if JSDraw is enabled if (pref && this.enableJSDraw) { if (pref === 'ketcher') { this.structureEditor = 'ketcher'; @@ -180,18 +221,19 @@ export class StructureEditorComponent implements OnInit, AfterViewInit, OnDestro this.structureEditor = 'ketcher'; } - if (this.configService && this.configService.configData && this.configService.configData.disableJSDraw ) { + // if JSDraw is NOT Enable or is disable + if (this.configService && this.configService.configData && this.configService.configData.disableJSDraw) { this.enableJSDraw = false; - this.structureEditor = 'ketcher'; - if (this.firstload && this.structureEditor === 'ketcher' ) { - document.getElementById("root").style.display=""; - this.waitForKetcherFirstLoad(); - this.firstload = false; - - } else if (this.firstload) { - this.firstload = false; - } - } else if (this.configService && this.configService.configData && this.configService.configData.disableKetcher ) { + this.structureEditor = 'ketcher'; + if (this.firstload && this.structureEditor === 'ketcher') { + document.getElementById("root").style.display = ""; + this.waitForKetcherFirstLoad(); + this.firstload = false; + + } else if (this.firstload) { + this.firstload = false; + } + } else if (this.configService && this.configService.configData && this.configService.configData.disableKetcher) { this.enableKetcher = !this.configService.configData.disableKetcher; if (!this.enableKetcher) { @@ -201,7 +243,7 @@ export class StructureEditorComponent implements OnInit, AfterViewInit, OnDestro this.editorSwitched.emit(this.structureEditor); - if ( !window['JSDraw'] && this.enableJSDraw) { + if (!window['JSDraw'] && this.enableJSDraw) { // this is extremely hacky but no way around it @@ -225,20 +267,115 @@ export class StructureEditorComponent implements OnInit, AfterViewInit, OnDestro document.getElementsByTagName('head')[0].appendChild(node); } } - - - for (let i = 0; i < this.ketcherUrls.length; i++) { - const node = document.createElement('script'); - node.src = this.ketcherUrls[i]; - node.type = 'text/javascript'; - node.async = false; - document.getElementsByTagName('head')[0].appendChild(node); + + + for (let i = 0; i < this.ketcherUrls.length; i++) { + const node = document.createElement('script'); + node.src = this.ketcherUrls[i]; + node.type = 'text/javascript'; + node.async = false; + document.getElementsByTagName('head')[0].appendChild(node); + } + + const node2 = document.createElement('link'); + node2.href = `${environment.baseHref || ''}assets/ketcher/static/css/main.3fc9c0f8.css`; + node2.rel = "stylesheet"; + document.getElementsByTagName('head')[0].appendChild(node2); + } + } + + createDivRootElement() { + if (this.structureEditor === "ketcher") { + let parentElement = document.getElementById('ketcherwrapper'); + let childElement = document.getElementById('root'); + if (parentElement) { + if (!childElement) { + const divElement = document.createElement("div"); + divElement.setAttribute("id", "root"); + + divElement.style.height = '618px'; + + divElement.style.clear = 'both'; + divElement.style.display = 'none'; + divElement.style.padding = '10px'; + + // append child to parent + parentElement.appendChild(divElement); + + window.addEventListener('click', this.listener); + this.overlayContainer = this.overlayContainerService.getContainerElement(); + this.editorSwitched.emit(this.structureEditor); + + this.ketcherReload(); + } + } + } + } + + ketcherReload() { + this.firstload = true; + + window.addEventListener('click', this.listener); + this.overlayContainer = this.overlayContainerService.getContainerElement(); + if (isPlatformBrowser(this.platformId)) { + + this.structureEditor = 'ketcher'; + + if (this.configService && this.configService.configData && this.configService.configData.disableJSDraw) { + this.enableJSDraw = false; + this.structureEditor = 'ketcher'; + + if (this.firstload && this.structureEditor === 'ketcher') { + document.getElementById("root").style.display = ""; + this.waitForKetcherFirstLoad(); + this.firstload = false; + + } else if (this.firstload) { + this.firstload = false; + } + } else if (this.configService && this.configService.configData && this.configService.configData.disableKetcher) { + this.enableKetcher = !this.configService.configData.disableKetcher; + + if (!this.enableKetcher) { + this.structureEditor = 'jsdraw'; + } + } + + this.editorSwitched.emit(this.structureEditor); + + for (let i = 0; i < this.ketcherUrls.length; i++) { + const node = document.createElement('script'); + node.src = this.ketcherUrls[i]; + node.type = 'text/javascript'; + node.async = false; + document.getElementsByTagName('head')[0].appendChild(node); + } + + const node2 = document.createElement('link'); + node2.href = `${environment.baseHref || ''}assets/ketcher/static/css/main.3fc9c0f8.css`; + node2.rel = "stylesheet"; + document.getElementsByTagName('head')[0].appendChild(node2); + + + this.getSketcher().activated = false; + + sessionStorage.setItem('gsrsStructureEditor', 'ketcher'); + if (!this.ketcherLoaded) { + this.ketcher = window['ketcher']; + this.ketcherLoaded = true; + + } + + if (this.firstload && this.structureEditor === 'ketcher') { + document.getElementById("root").style.display = ""; + this.waitForKetcherFirstLoad(); + this.firstload = false; + + } else if (this.firstload) { + this.firstload = false; + } - const node2 = document.createElement('link'); - node2.href = `${environment.baseHref || ''}assets/ketcher/static/css/main.3fc9c0f8.css`; - node2.rel="stylesheet"; - document.getElementsByTagName('head')[0].appendChild(node2); } } @@ -246,61 +383,60 @@ export class StructureEditorComponent implements OnInit, AfterViewInit, OnDestro // now unused due to async issues with jsdraw } - getSketcher(){ + getSketcher() { var skt; - if(window['JSDraw2']){ - for(var k in window['JSDraw2'].Editor._allitems){ - skt= window['JSDraw2'].Editor._allitems[k]; + if (window['JSDraw2']) { + for (var k in window['JSDraw2'].Editor._allitems) { + skt = window['JSDraw2'].Editor._allitems[k]; + } } - } return skt; - } - + } toggleEditor() { - if (this.structureEditor === 'ketcher' ) { - this.getSketcher().activated=true; + if (this.structureEditor === 'ketcher') { + this.getSketcher().activated = true; this.editor.getMolfile().pipe(take(1)).subscribe(Response => { this.structureEditor = 'jsdraw'; - // this.editor = new EditorImplementation(this.ketcher, this.jsdraw, 'jsdraw'); - this.editor = new EditorImplementation(null, this.jsdraw); + // this.editor = new EditorImplementation(this.ketcher, this.jsdraw, 'jsdraw'); + this.editor = new EditorImplementation(null, this.jsdraw); this.structureService.interpretStructure(Response).subscribe(resp => { this.editorOnLoad.emit(this.editor); this.editorSwitched.emit(this.structureEditor); this.jsdraw.setMolfile(resp.structure.molfile); - sessionStorage.setItem('gsrsStructureEditor', 'jsdraw'); - document.getElementById("root").style.display="none"; + sessionStorage.setItem('gsrsStructureEditor', 'jsdraw'); + document.getElementById("root").style.display = "none"; }); - }); + }); } else { - this.getSketcher().activated=false; + this.getSketcher().activated = false; sessionStorage.setItem('gsrsStructureEditor', 'ketcher'); - if(!this.ketcherLoaded) { - this.ketcher = window['ketcher']; - this.ketcherLoaded = true; - - } + if (!this.ketcherLoaded) { + this.ketcher = window['ketcher']; + this.ketcherLoaded = true; - this.editor.getMolfile().pipe(take(1)).subscribe(Response => { - this.structureEditor = 'ketcher'; - // this.editor = new EditorImplementation(this.ketcher, this.jsdraw, 'ketcher'); - this.editor = new EditorImplementation(this.ketcher); - this.structureService.interpretStructure(Response).subscribe(resp => { - this.editorOnLoad.emit(this.editor); - this.editorSwitched.emit(this.structureEditor); - this.ketcher.setMolecule(resp.structure.molfile); + } + + this.editor.getMolfile().pipe(take(1)).subscribe(Response => { + this.structureEditor = 'ketcher'; + // this.editor = new EditorImplementation(this.ketcher, this.jsdraw, 'ketcher'); + this.editor = new EditorImplementation(this.ketcher); + this.structureService.interpretStructure(Response).subscribe(resp => { + this.editorOnLoad.emit(this.editor); + this.editorSwitched.emit(this.structureEditor); + this.ketcher.setMolecule(resp.structure.molfile); + }); + sessionStorage.setItem('gsrsStructureEditor', 'ketcher'); + document.getElementById("root").style.display = "none"; }); - sessionStorage.setItem('gsrsStructureEditor', 'ketcher'); - document.getElementById("root").style.display="none"; - }); this.structureEditor = 'ketcher'; - document.getElementById("root").style.display=""; + document.getElementById("root").style.display = ""; } } @@ -309,67 +445,66 @@ export class StructureEditorComponent implements OnInit, AfterViewInit, OnDestro this.jsdrawLoaded = true; // this.editor = new EditorImplementation(this.ketcher, this.jsdraw, 'jsdraw'); this.editor = new EditorImplementation(null, this.jsdraw); - this.editorOnLoad.emit(this.editor); - this.editorSwitched.emit(this.structureEditor); + this.editorOnLoad.emit(this.editor); + this.editorSwitched.emit(this.structureEditor); - if (this.firstload && this.structureEditor === 'ketcher' ) { - document.getElementById("root").style.display=""; - this.waitForKetcherFirstLoad(); - this.firstload = false; + if (this.firstload && this.structureEditor === 'ketcher') { + document.getElementById("root").style.display = ""; + this.waitForKetcherFirstLoad(); + this.firstload = false; + + } else if (this.firstload) { + this.firstload = false; + } - } else if (this.firstload) { - this.firstload = false; - } - } async waitForKetcherFirstLoad(): Promise { await this.executeOnceNotNullOrUndefined(() => window['ketcher'], (obj) => { - setTimeout(() => { - this.ketcher = window['ketcher']; - this.ketcherLoaded = true; - document.getElementById("root").style.display=""; - // this.editor = new EditorImplementation(this.ketcher, this.jsdraw, 'ketcher'); - this.editor = new EditorImplementation(this.ketcher); - this.editorOnLoad.emit(this.editor); - this.editorSwitched.emit(this.structureEditor); + setTimeout(() => { + this.ketcher = window['ketcher']; + this.ketcherLoaded = true; + document.getElementById("root").style.display = ""; + // this.editor = new EditorImplementation(this.ketcher, this.jsdraw, 'ketcher'); + this.editor = new EditorImplementation(this.ketcher); + this.editorOnLoad.emit(this.editor); + this.editorSwitched.emit(this.structureEditor); - /* this.ketcher.editor.subscribe('change', operations => { - this.ketcher.getMolfile().then(result => { - this.getSketcher().setFile(result, "mol"); - }) - if(!(operations.length == 1 && operations[0].operation == 'Load canvas')){ + /* this.ketcher.editor.subscribe('change', operations => { + this.ketcher.getMolfile().then(result => { + this.getSketcher().setFile(result, "mol"); + }) + if(!(operations.length == 1 && operations[0].operation == 'Load canvas')){ + } + + + });*/ + if (this.enableJSDraw) { + this.ketcher.editor.event.change.handlers.push({ + f: (c) => { + this.ketcher.getMolfile().then(result => { + let mfile = [null]; + mfile[0] = result; + this.getSketcher().setFile(mfile[0], "mol"); + }) + } + }); } - - - });*/ - if (this.enableJSDraw){ - this.ketcher.editor.event.change.handlers.push({f:(c)=>{ - this.ketcher.getMolfile().then(result => { - let mfile = [null]; - mfile[0]= result; - this.getSketcher().setFile(mfile[0], "mol"); - }) - } - }); - } - }, 150); - - }); -} + }, 150); + + }); + } executeOnceNotNullOrUndefined(objProvider: () => T | null | undefined, callback: (obj: T) => void, interval: number = 100): void { const intervalId = setInterval(() => { - const obj = objProvider(); - if (obj !== null && obj !== undefined) { - clearInterval(intervalId); - callback(obj); - }else { - } + const obj = objProvider(); + if (obj !== null && obj !== undefined) { + clearInterval(intervalId); + callback(obj); + } else { + } }, interval); -} - - + } waitForNonNull(variable: () => any): Promise { return new Promise((resolve) => { @@ -378,7 +513,7 @@ export class StructureEditorComponent implements OnInit, AfterViewInit, OnDestro clearInterval(interval); resolve(); } - }, 100); + }, 100); }); } @@ -388,7 +523,7 @@ export class StructureEditorComponent implements OnInit, AfterViewInit, OnDestro onDropHandler(object: any): void { //rule out tiny icons / images accidentally being dragged from jsdraw UI - if(object.backup.size < 700) { + if (object.backup.size < 700) { this.canvasMessage = 'The selected file is too small to be read (<700 bytes)'; } if (object.invalidFlag) { @@ -399,7 +534,6 @@ export class StructureEditorComponent implements OnInit, AfterViewInit, OnDestro } } - sendToMolvec(img: string) { this.canvasMessage = ''; this.loadingService.setLoading(true); @@ -407,21 +541,21 @@ export class StructureEditorComponent implements OnInit, AfterViewInit, OnDestro const mol = response.molfile; if (this.ketcher && this.structureEditor === 'ketcher') { this.ketcher.setMolecule(mol); + setTimeout(() => { + this.editor.setMolecule(mol); + }, 100); + this.loadedMolfile.emit(mol); + + this.loadingService.setLoading(false); + this.structureService.molvec(img).subscribe(resp => { setTimeout(() => { - this.editor.setMolecule(mol); + this.editor.setMolecule(resp.molfile); + this.ketcher.setMolecule(mol); }, 100); - this.loadedMolfile.emit(mol); - + }, error => { + this.canvasMessage = 'Structure not detectable'; this.loadingService.setLoading(false); - this.structureService.molvec(img).subscribe(resp => { - setTimeout(() => { - this.editor.setMolecule(resp.molfile); - this.ketcher.setMolecule(mol); - }, 100); - }, error => { - this.canvasMessage = 'Structure not detectable'; - this.loadingService.setLoading(false); - }); + }); } else { @@ -430,7 +564,7 @@ export class StructureEditorComponent implements OnInit, AfterViewInit, OnDestro this.loadedMolfile.emit(mol); } - + }, error => { this.canvasMessage = 'Structure not detectable'; this.loadingService.setLoading(false); @@ -497,14 +631,14 @@ export class StructureEditorComponent implements OnInit, AfterViewInit, OnDestro this.canvasMessage = ''; this.loadingService.setLoading(true); this.structureService.interpretStructure(text).subscribe(response => { - + if (response.structure && response.structure.molfile) { - - this.editor.setMolecule(response.structure.molfile); - + + this.editor.setMolecule(response.structure.molfile); + this.loadedMolfile.emit(response.structure.molfile); - if(response.structure.smiles === '') { + if (response.structure.smiles === '') { this.canvasMessage = 'empty or invalid structure pasted'; } } else { @@ -512,29 +646,27 @@ export class StructureEditorComponent implements OnInit, AfterViewInit, OnDestro } this.loadingService.setLoading(false); - },error =>{ + }, error => { this.loadingService.setLoading(false); this.canvasMessage = 'empty or invalid structure pasted'; }); } } } - - } cleanStructure() { - let molfile =''; + let molfile = ''; this.editor.getMolfile().subscribe(response => { molfile = response; - if (molfile != null && molfile !== '') { - this.structureService.interpretStructure(molfile).pipe(take(1)).subscribe(response => { - if (response && response.structure && response.structure.smiles) { - this.cleanStructureSmiles(response.structure.smiles); - } - }); - } - }); + if (molfile != null && molfile !== '') { + this.structureService.interpretStructure(molfile).pipe(take(1)).subscribe(response => { + if (response && response.structure && response.structure.smiles) { + this.cleanStructureSmiles(response.structure.smiles); + } + }); + } + }); } cleanStructureSmiles(smiles: string) { @@ -547,46 +679,44 @@ export class StructureEditorComponent implements OnInit, AfterViewInit, OnDestro } } - standardize(standard: string): void { this.loadingService.setLoading(true); - let mol =''; + let mol = ''; this.editor.getMolfile().pipe(take(1)).subscribe(response => { mol = response; - this.structureService.interpretStructure(mol, '', standard).pipe(take(1)).subscribe((response: any) => { - if (response && response.structure && response.structure.molfile) { - this.editor.setMolecule(response.structure.molfile); - } - this.loadingService.setLoading(false); - }, () => {this.loadingService.setLoading(false); }); - }); -} - + this.structureService.interpretStructure(mol, '', standard).pipe(take(1)).subscribe((response: any) => { + if (response && response.structure && response.structure.molfile) { + this.editor.setMolecule(response.structure.molfile); + } + this.loadingService.setLoading(false); + }, () => { this.loadingService.setLoading(false); }); + }); + } -openMolvecImportDialog(): void { - const dialogRef = this.dialog.open(MolvecModalComponent, { - height: 'auto', - width: '650px', - data: {} - }); - this.overlayContainer.style.zIndex = '1002'; + openMolvecImportDialog(): void { + const dialogRef = this.dialog.open(MolvecModalComponent, { + height: 'auto', + width: '650px', + data: {} + }); + this.overlayContainer.style.zIndex = '1002'; - dialogRef.afterClosed().subscribe((response?: any) => { - this.overlayContainer.style.zIndex = null; - if (response != null) { - if (response.type = "img") { - this.createImage(response.file); - } - if (response.type = "text") { - this.structureService.interpretStructure(response.file).subscribe(response => { - if (response.structure && response.structure.molfile) { - this.ketcher.setMolecule(response.structure.molfile); - this.loadedMolfile.emit(response.structure.molfile); - } - }); + dialogRef.afterClosed().subscribe((response?: any) => { + this.overlayContainer.style.zIndex = null; + if (response != null) { + if (response.type = "img") { + this.createImage(response.file); + } + if (response.type = "text") { + this.structureService.interpretStructure(response.file).subscribe(response => { + if (response.structure && response.structure.molfile) { + this.ketcher.setMolecule(response.structure.molfile); + this.loadedMolfile.emit(response.structure.molfile); + } + }); + } } - } - }, () => { }); -} + }, () => { }); + } } diff --git a/src/app/core/structure/structure.service.ts b/src/app/core/structure/structure.service.ts index b92268ac0..b333c0192 100644 --- a/src/app/core/structure/structure.service.ts +++ b/src/app/core/structure/structure.service.ts @@ -1,12 +1,13 @@ import { Injectable } from '@angular/core'; import { DomSanitizer, SafeUrl } from '@angular/platform-browser'; import { ConfigService } from '../config/config.service'; -import { Observable, timeout } from 'rxjs'; +import { Observable, timeout, BehaviorSubject} from 'rxjs'; import { HttpClient, HttpParams } from '@angular/common/http'; import { SubstanceDetail, SubstanceStructure, SubstanceMoiety } from '../substance/substance.model'; import { ResolverResponse } from './structure-post-response.model'; import { InterpretStructureResponse } from './structure-post-response.model'; import { ControlledVocabularyService } from '@gsrs-core/controlled-vocabulary'; + @Injectable({ providedIn: 'root' }) @@ -19,6 +20,22 @@ export class StructureService { ) { } + private _isCalledFromRegisterSubstance = new BehaviorSubject(false); // Initial value + readonly isCalledFromRegisterSubstance$ = this._isCalledFromRegisterSubstance.asObservable(); // Expose as an Observable + + private _reloadKetcher = new BehaviorSubject(false); // Initial value + readonly reloadKetcher$ = this._reloadKetcher.asObservable(); // Expose as an Observable + + isCalledFromRegisterSubstance(newValue: boolean) { + this._isCalledFromRegisterSubstance.next(newValue); // Update the value and notify subscribers + } + + updateReloadKetcher(uploadKetcher: boolean) { + if (uploadKetcher == true) { + this._reloadKetcher.next(uploadKetcher); + } + } + getSafeStructureImgUrl(structureId: string, size: number = 150): SafeUrl { const imgUrl = `${this.configService.configData.apiBaseUrl}img/${structureId}.svg?size=${size.toString()}`; return this.sanitizer.bypassSecurityTrustUrl(imgUrl); diff --git a/src/app/core/substance-form/structure/substance-form-structure-card.component.html b/src/app/core/substance-form/structure/substance-form-structure-card.component.html index b7cef3f69..7e4e8f66f 100644 --- a/src/app/core/substance-form/structure/substance-form-structure-card.component.html +++ b/src/app/core/substance-form/structure/substance-form-structure-card.component.html @@ -4,8 +4,9 @@

    Draw or import a structure using Ketcher. Then, if any features are detected a table will be automatically displayed under the editor.

    -
    - +
    +
    diff --git a/src/app/core/substance-form/structure/substance-form-structure-card.component.scss b/src/app/core/substance-form/structure/substance-form-structure-card.component.scss index 735d4eacd..7fcb05842 100644 --- a/src/app/core/substance-form/structure/substance-form-structure-card.component.scss +++ b/src/app/core/substance-form/structure/substance-form-structure-card.component.scss @@ -17,7 +17,7 @@ } .button-container { - margin-top: -50px; + margin-top: -40px; } diff --git a/src/app/core/substance-form/structure/substance-form-structure-card.component.ts b/src/app/core/substance-form/structure/substance-form-structure-card.component.ts index f33471097..7cea389f1 100644 --- a/src/app/core/substance-form/structure/substance-form-structure-card.component.ts +++ b/src/app/core/substance-form/structure/substance-form-structure-card.component.ts @@ -48,6 +48,7 @@ export class SubstanceFormStructureCardComponent extends SubstanceFormBase imple featuresOnly = false; hideFeaturesTable = false; structureEditSearch = true; + calledFrom = 'registerSubstance'; StructureFeaturePriority = [ 'Category Score', 'Sum Of Scores', diff --git a/src/app/fda/config/config.json b/src/app/fda/config/config.json index 8e5f40e53..4f1889e88 100644 --- a/src/app/fda/config/config.json +++ b/src/app/fda/config/config.json @@ -14,6 +14,7 @@ "primaryCode": "BDNUM", "useDataUrl": false, "showCrossEntitySearchDropdown": true, + "disableJSDraw": false, "restApiPrefix": "/ginas/app", "authenticateAs": { "apiUsername": null, From 8616b0592f9113f08dd2805cc521492045d53c04 Mon Sep 17 00:00:00 2001 From: Mitch Miller Date: Mon, 4 Aug 2025 14:13:23 -0400 Subject: [PATCH 064/408] complete fix of issue --- .../substance-form.component.ts | 59 +++++++++---------- 1 file changed, 28 insertions(+), 31 deletions(-) diff --git a/src/app/core/substance-form/substance-form.component.ts b/src/app/core/substance-form/substance-form.component.ts index 4dc39a717..0548ba14d 100644 --- a/src/app/core/substance-form/substance-form.component.ts +++ b/src/app/core/substance-form/substance-form.component.ts @@ -49,7 +49,7 @@ import {AdminService} from '@gsrs-core/admin/admin.service'; import {MatButtonToggleChange} from "@angular/material/button-toggle"; import {tr} from "cronstrue/dist/i18n/locales/tr"; import { Location } from '@angular/common'; - +import jp from 'jsonpath'; @Component({ selector: 'app-substance-form', @@ -1149,7 +1149,7 @@ export class SubstanceFormComponent implements OnInit, AfterViewInit, OnDestroy const old = oldraw; - const idHolders = defiant.json.search(old, '//*[id]'); + const idHolders = jp.query(old, '$..[?(@.id)]'); const idMap = {}; for (let i = 0; i < idHolders.length; i++) { const oid = idHolders[i].id; @@ -1162,7 +1162,7 @@ export class SubstanceFormComponent implements OnInit, AfterViewInit, OnDestroy } } - const uuidHolders = defiant.json.search(old, '//*[uuid]'); + const uuidHolders = jp.query(old, '$..[?(@.uuid)]'); const _map = {}; for (let i = 0; i < uuidHolders.length; i++) { const ouuid = uuidHolders[i].uuid; @@ -1180,7 +1180,7 @@ export class SubstanceFormComponent implements OnInit, AfterViewInit, OnDestroy } } } - const refHolders = defiant.json.search(old, '//*[references]'); + const refHolders = jp.query(old, '$..[?(@.references)]'); for (let i = 0; i < refHolders.length; i++) { const refs = refHolders[i].references; for (let j = 0; j < refs.length; j++) { @@ -1191,7 +1191,8 @@ export class SubstanceFormComponent implements OnInit, AfterViewInit, OnDestroy refs[j] = _map[or]; } } - defiant.json.search(old, '//*[uuid]'); + //removed redundant search 4 August 2025 MAM + //defiant.json.search(old, '//*[uuid]'); let remove = ['BDNUM']; if (this.configService.configData && this.configService.configData.filteredDuplicationCodes) { remove = this.configService.configData.filteredDuplicationCodes; @@ -1201,7 +1202,7 @@ export class SubstanceFormComponent implements OnInit, AfterViewInit, OnDestroy codeSystem: code }); }) - const createHolders = defiant.json.search(old, '//*[created]'); + const createHolders = jp.query(old, '$..[?(@.created)]'); for (let i = 0; i < createHolders.length; i++) { const rec = createHolders[i]; delete rec['created']; @@ -1210,7 +1211,7 @@ export class SubstanceFormComponent implements OnInit, AfterViewInit, OnDestroy delete rec['lastEditedBy']; } - const originHolders = defiant.json.search(old, '//*[originatorUuid]'); + const originHolders = jp.query(old, '$..[?(@.originatorUuid)]'); for (let i = 0; i < originHolders.length; i++) { const rec = originHolders[i]; delete rec['originatorUuid']; @@ -1235,35 +1236,31 @@ export class SubstanceFormComponent implements OnInit, AfterViewInit, OnDestroy delete old['$$update']; delete old['changeReason']; + const refSet = {}; - if (true) { - const refSet = {}; - - const refHolders2 = defiant.json.search(old, '//*[references]'); - for (let i = 0; i < refHolders2.length; i++) { - const refs = refHolders2[i].references; - for (let j = 0; j < refs.length; j++) { - const or = refs[j]; - if (typeof or === 'object') { - continue; - } - refSet[or] = true; + const refHolders2 = jp.query(old, '$..[?(@.references)]'); + for (let i = 0; i < refHolders2.length; i++) { + const refs = refHolders2[i].references; + for (let j = 0; j < refs.length; j++) { + const or = refs[j]; + if (typeof or === 'object') { + continue; } + refSet[or] = true; } + } - const nrefs = _.chain(old.references) - .filter(function (ref) { - if (refSet[ref.uuid]) { - return true; - } else { - return false; - } - }) - .value(); - - old.references = nrefs; + const nrefs = _.chain(old.references) + .filter(function (ref) { + if (refSet[ref.uuid]) { + return true; + } else { + return false; + } + }) + .value(); - } + old.references = nrefs; return old; } From 23df4b7bf9f281e0efa80e256c88e05420721aa6 Mon Sep 17 00:00:00 2001 From: Newatia Date: Tue, 5 Aug 2025 19:07:31 -0400 Subject: [PATCH 065/408] added disclaimer in ketcher --- .../core/structure-editor/structure-editor.component.html | 5 ++++- .../core/structure-editor/structure-editor.component.ts | 8 ++++++-- .../substance-form-structure-card.component.html | 2 +- .../structure/substance-form-structure-card.component.ts | 5 +++++ 4 files changed, 16 insertions(+), 4 deletions(-) diff --git a/src/app/core/structure-editor/structure-editor.component.html b/src/app/core/structure-editor/structure-editor.component.html index 2e1704552..7b57a4f70 100644 --- a/src/app/core/structure-editor/structure-editor.component.html +++ b/src/app/core/structure-editor/structure-editor.component.html @@ -13,10 +13,13 @@
    - +
    +
    + DISCLAIMER: {{disclaimerMessage}} +
    Use copy dropdown menu on Ketcher editor and NOT ctrl + c to copy structure.
    diff --git a/src/app/core/structure-editor/structure-editor.component.ts b/src/app/core/structure-editor/structure-editor.component.ts index 34ea7c064..23c14a938 100644 --- a/src/app/core/structure-editor/structure-editor.component.ts +++ b/src/app/core/structure-editor/structure-editor.component.ts @@ -53,6 +53,7 @@ export class StructureEditorComponent implements OnInit, AfterViewInit, OnDestro ketcherWindowActive = false; firstload = true; calledFromComponent: string; + disclaimerMessage: string; private overlayContainer: HTMLElement; @ViewChild('structure_canvas', { static: false }) myCanvas: ElementRef; @@ -142,6 +143,11 @@ export class StructureEditorComponent implements OnInit, AfterViewInit, OnDestro this.calledFromComponent = calledFromComp; } + @Input() + set disclaimer(disclaimerMess: any) { + this.disclaimerMessage = disclaimerMess; + } + listener = () => { var elmR = document.getElementById("root"); if (this.structureEditor === "ketcher") { @@ -187,7 +193,6 @@ export class StructureEditorComponent implements OnInit, AfterViewInit, OnDestro this.structureService.reloadKetcher$.subscribe((reloadKetcher) => { if (reloadKetcher === true) { if (this.calledFromComponent && this.calledFromComponent === 'registerSubstance') { - console.log(this.calledFromComponent + ' reload ketcher:', reloadKetcher); this.createDivRootElement(); } } @@ -294,7 +299,6 @@ export class StructureEditorComponent implements OnInit, AfterViewInit, OnDestro divElement.setAttribute("id", "root"); divElement.style.height = '618px'; - divElement.style.clear = 'both'; divElement.style.display = 'none'; divElement.style.padding = '10px'; diff --git a/src/app/core/substance-form/structure/substance-form-structure-card.component.html b/src/app/core/substance-form/structure/substance-form-structure-card.component.html index 7e4e8f66f..2e59a3665 100644 --- a/src/app/core/substance-form/structure/substance-form-structure-card.component.html +++ b/src/app/core/substance-form/structure/substance-form-structure-card.component.html @@ -6,7 +6,7 @@

    Draw or import a structure using Ketcher. Then, if any features are detected

    + [calledFrom]="calledFrom" [disclaimer]="disclaimer" style="z-index: 9999">
    diff --git a/src/app/core/substance-form/structure/substance-form-structure-card.component.ts b/src/app/core/substance-form/structure/substance-form-structure-card.component.ts index 7cea389f1..c279b58dc 100644 --- a/src/app/core/substance-form/structure/substance-form-structure-card.component.ts +++ b/src/app/core/substance-form/structure/substance-form-structure-card.component.ts @@ -49,6 +49,7 @@ export class SubstanceFormStructureCardComponent extends SubstanceFormBase imple hideFeaturesTable = false; structureEditSearch = true; calledFrom = 'registerSubstance'; + disclaimer: string; StructureFeaturePriority = [ 'Category Score', 'Sum Of Scores', @@ -110,6 +111,10 @@ export class SubstanceFormStructureCardComponent extends SubstanceFormBase imple this.substanceType = def.substanceClass; if (this.substanceType === 'polymer') { this.menuLabelUpdate.emit('Idealized Structure'); + + // Display this message under the Ketcher Editor when Registering/Updating Polymer + this.disclaimer = "This is disclaimer"; + const idealStructSubscription = this.substanceFormStructureService.substanceIdealizedStructure.subscribe(structure => { if (structure) { this.structure = structure; From 69a7b18ad5d37bcac394b5f48dbb5b78c47e2945 Mon Sep 17 00:00:00 2001 From: Mitch Miller Date: Wed, 6 Aug 2025 12:33:11 -0400 Subject: [PATCH 066/408] fix for NGSRS-450 --- .../advanced-search.component.ts | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/src/app/fda/advanced-search/advanced-search.component.ts b/src/app/fda/advanced-search/advanced-search.component.ts index 2c27f92df..5ebf1367b 100644 --- a/src/app/fda/advanced-search/advanced-search.component.ts +++ b/src/app/fda/advanced-search/advanced-search.component.ts @@ -803,7 +803,7 @@ export class AdvancedSearchComponent implements OnInit, OnDestroy { let mol = ''; this.editor.getMolfile().pipe(take(1)).subscribe(response => { mol = response; - if (mol && mol.length > 72) { + if (this.isPossibleMol(mol)) { this.structureService.interpretStructure(mol).subscribe((response: InterpretStructureResponse) => { const eventLabel = !environment.isAnalyticsPrivate && response.structure.smiles || 'structure search term'; // this.gaService.sendEvent('structureSearch', 'button:search', eventLabel); @@ -845,6 +845,7 @@ export class AdvancedSearchComponent implements OnInit, OnDestroy { // If no structure search, do this else { + console.log('no molecule found'); this.router.navigate(['/browse-substance'], navigationExtras); // } } @@ -899,6 +900,21 @@ export class AdvancedSearchComponent implements OnInit, OnDestroy { }); } + isPossibleMol(testMol: string) : boolean { + const blankCountsLine = " 0 0 0 0 0 0 0 0 0 0999 V2000"; + if( !testMol || testMol === null || !testMol.length || testMol.length < 100){ + return false; + } + let lines = testMol.split('\n'); + if( lines === null || lines.length < 4){ + return false; + } + if(lines[3] === blankCountsLine) { + return false; + } + return true; + } + searchTypeSelected(event): void { this.searchType = event.value; From b5e5d88ffc2e20c5168cebd0acedffc8af4517ff Mon Sep 17 00:00:00 2001 From: Mitch Miller Date: Wed, 6 Aug 2025 12:45:38 -0400 Subject: [PATCH 067/408] added flex plus and exact plus as structure search options on the advanced search page --- src/app/fda/advanced-search/advanced-search.component.html | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/app/fda/advanced-search/advanced-search.component.html b/src/app/fda/advanced-search/advanced-search.component.html index 247a69f93..3643c41b4 100644 --- a/src/app/fda/advanced-search/advanced-search.component.html +++ b/src/app/fda/advanced-search/advanced-search.component.html @@ -231,9 +231,15 @@

    Exact + + Exact Plus + Flex + + Flex Plus +
    From f253f3110031143bc21777bde0d97debdefa3479 Mon Sep 17 00:00:00 2001 From: Newatia Date: Wed, 6 Aug 2025 13:43:42 -0400 Subject: [PATCH 068/408] added datepicker --- .../product-form/product-form.component.html | 8 ++ .../product-form/product-form.component.ts | 99 +++++++++++++------ src/app/fda/product/product.module.ts | 8 +- 3 files changed, 81 insertions(+), 34 deletions(-) diff --git a/src/app/fda/product/product-form/product-form.component.html b/src/app/fda/product/product-form/product-form.component.html index 5f0d38f4b..773abf1c5 100644 --- a/src/app/fda/product/product-form/product-form.component.html +++ b/src/app/fda/product/product-form/product-form.component.html @@ -168,7 +168,15 @@
    +
    + + Effective Date (mm/dd/yyyy) + + + + + diff --git a/src/app/fda/product/product-form/product-form.component.ts b/src/app/fda/product/product-form/product-form.component.ts index d57cae9e4..4c6c56939 100644 --- a/src/app/fda/product/product-form/product-form.component.ts +++ b/src/app/fda/product/product-form/product-form.component.ts @@ -1,25 +1,29 @@ import { Component, OnInit, AfterViewInit, OnDestroy, ViewEncapsulation } from '@angular/core'; -import { ProductService } from '../service/product.service'; import { ActivatedRoute, Router } from '@angular/router'; -import { LoadingService } from '@gsrs-core/loading'; -import { MainNotificationService } from '@gsrs-core/main-notification'; -import { AppNotification, NotificationType } from '@gsrs-core/main-notification'; -import { GoogleAnalyticsService } from '@gsrs-core/google-analytics'; -import { UtilsService } from '@gsrs-core/utils/utils.service'; -import { AuthService } from '@gsrs-core/auth/auth.service'; -import { ControlledVocabularyService } from '../../../core/controlled-vocabulary/controlled-vocabulary.service'; -import { Product, ValidationMessage } from '../model/product.model'; -import { DomSanitizer, SafeUrl } from '@angular/platform-browser'; +import { MatDialog } from '@angular/material/dialog'; +import { MatDatepickerInputEvent } from '@angular/material/datepicker'; +import { Title, DomSanitizer, SafeUrl } from '@angular/platform-browser'; import { Subscription } from 'rxjs'; -import * as moment from 'moment'; -import * as defiant from '@gsrs-core/../../../node_modules/defiant.js/dist/defiant.min.js'; -import { Title } from '@angular/platform-browser'; import { take } from 'rxjs/operators'; -import { MatDialog } from '@angular/material/dialog'; import { OverlayContainer } from '@angular/cdk/overlay'; +import * as moment from 'moment'; + +/* GSRS Core Imports */ +import { LoadingService } from '@gsrs-core/loading'; +import { UtilsService } from '@gsrs-core/utils/utils.service'; +import { AuthService } from '@gsrs-core/auth/auth.service'; +import { ControlledVocabularyService } from '@gsrs-core/controlled-vocabulary/controlled-vocabulary.service'; +import { MainNotificationService } from '@gsrs-core/main-notification'; +import { GoogleAnalyticsService } from '@gsrs-core/google-analytics'; +import { AppNotification, NotificationType } from '@gsrs-core/main-notification'; import { SubstanceEditImportDialogComponent } from '@gsrs-core/substance-edit-import-dialog/substance-edit-import-dialog.component'; import { JsonDialogFdaComponent } from '../../json-dialog-fda/json-dialog-fda.component'; import { ConfirmDialogComponent } from '../../confirm-dialog/confirm-dialog.component'; +import * as defiant from '@gsrs-core/../../../node_modules/defiant.js/dist/defiant.min.js'; + +/* GSRS Product Imports */ +import { ProductService } from '../service/product.service'; +import { Product, ValidationMessage } from '../model/product.model'; @Component({ selector: 'app-product-form', @@ -29,30 +33,39 @@ import { ConfirmDialogComponent } from '../../confirm-dialog/confirm-dialog.comp export class ProductFormComponent implements OnInit, AfterViewInit, OnDestroy { - product: Product; - id?: number; - isLoading = true; - showSubmissionMessages = false; - submissionMessage: string; - validationMessages: Array = []; - validationResult = false; + /* Array data type */ private subscriptions: Array = []; - copy: string; - private overlayContainer: HTMLElement; - serverError: boolean; - isDisableData = false; + validationMessages: Array = []; + provenanceFieldMessage: Array = []; + effectiveTimeMessage: any[][] = []; + + /* object data type */ + product: Product; + overlayContainer: HTMLElement; + downloadJsonHref: any; + + /* string data type */ username = null; title = null; - isAdmin = false; - disableMarketingCategoryCode = true; expiryDateMessage = ''; manufactureDateMessage = ''; viewProductUrl = ''; message = ''; - downloadJsonHref: any; + copy: string; + submissionMessage: string; jsonFileName: string; - provenanceFieldMessage: Array = []; - effectiveTimeMessage: any[][] = []; + + /* number data type */ + id?: number; + + /* boolean data type */ + isAdmin = false; + isLoading = true; + isDisableData = false; + showSubmissionMessages = false; + validationResult = false; + disableMarketingCategoryCode = true; + serverError: boolean; constructor( private productService: ProductService, @@ -505,7 +518,7 @@ export class ProductFormComponent implements OnInit, AfterViewInit, OnDestroy { data: data }); - // this.overlayContainer.style.zIndex = '1002'; + this.overlayContainer.style.zIndex = '1002'; const dialogSubscription = dialogRef.afterClosed().subscribe(response => { }); this.subscriptions.push(dialogSubscription); @@ -821,6 +834,30 @@ export class ProductFormComponent implements OnInit, AfterViewInit, OnDestroy { this.subscriptions.push(cvSubscription); } + dateChangeEffectiveDate(event: MatDatepickerInputEvent): void { + if (event.value) { + this.product.effectiveDate = moment(event.value).format('MM/DD/YYYY'); + } + } + + onDateChange + + openedChange(opened: boolean): void { + if (opened) { + this.increaseOverlayZindex(); + } else { + this.decreaseOverlayZindex(); + } + } + + increaseOverlayZindex(): void { + this.overlayContainer.style.zIndex = '1002'; + } + + decreaseOverlayZindex(): void { + this.overlayContainer.style.zIndex = null; + } + scrub(oldraw: any): any { const old = oldraw; const idHolders = defiant.json.search(old, '//*[id]'); diff --git a/src/app/fda/product/product.module.ts b/src/app/fda/product/product.module.ts index f32e11ddd..91271f631 100644 --- a/src/app/fda/product/product.module.ts +++ b/src/app/fda/product/product.module.ts @@ -27,6 +27,8 @@ import { MatButtonToggleModule } from '@angular/material/button-toggle'; import { MatTooltipModule } from '@angular/material/tooltip'; import { MatProgressSpinnerModule } from '@angular/material/progress-spinner'; import { MatBottomSheetModule } from '@angular/material/bottom-sheet'; +import { MatDatepickerModule } from '@angular/material/datepicker'; +import { MatNativeDateModule } from '@angular/material/core'; import { OverlayModule } from '@angular/cdk/overlay'; /* GSRS Core Imports */ @@ -41,7 +43,6 @@ import { CrossEntitySearchModule } from '../cross-entity-search/cross-entity-sea /* GSRS Product Imports */ import { ProductTextSearchModule } from './product-text-search/product-text-search.module'; import { ProductService } from './service/product.service'; - import { ProductsBrowseComponent } from './products-browse/products-browse.component'; import { ProductDetailsBaseComponent } from './product-details/product-details-base.component'; import { ProductDetailsComponent } from './product-details/product-details/product-details.component'; @@ -115,6 +116,8 @@ const productRoutes: Routes = [ MatTabsModule, MatBottomSheetModule, MatProgressSpinnerModule, + MatDatepickerModule, + MatNativeDateModule, FormsModule, ReactiveFormsModule, OverlayModule, @@ -146,8 +149,7 @@ const productRoutes: Routes = [ providers: [ CanActivateRegisterProductFormComponent, CanActivateUpdateProductFormComponent, - CanDeactivateProductFormComponent, - ActivateProductsComponent + CanDeactivateProductFormComponent ] }) From 25114eef050cb0ff015cc08e40f7438e5d9143fb Mon Sep 17 00:00:00 2001 From: Newatia Date: Wed, 6 Aug 2025 14:18:11 -0400 Subject: [PATCH 069/408] added ketcher disclaimer --- src/app/core/config/config.model.ts | 3 ++- .../structure/substance-form-structure-card.component.ts | 7 ++++++- src/app/fda/config/config.json | 1 + 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/src/app/core/config/config.model.ts b/src/app/core/config/config.model.ts index aaacdeca7..ff993e74d 100644 --- a/src/app/core/config/config.model.ts +++ b/src/app/core/config/config.model.ts @@ -78,11 +78,12 @@ export interface Config { citationMapping?: { [code: string]: string; }; - structureEditor?: 'ketcher' | 'jsdraw'; nameFormPageSizeOptions?: Array; nameFormPageSizeDefault?: number; + structureEditor?: 'ketcher' | 'jsdraw'; disableJSDraw?: boolean; disableKetcher?: boolean; + ketcherDisclaimer?: string; useApprovalAPI?: boolean; dummyWhoami?: Auth; enableStructureFeatures?: boolean; diff --git a/src/app/core/substance-form/structure/substance-form-structure-card.component.ts b/src/app/core/substance-form/structure/substance-form-structure-card.component.ts index c279b58dc..5a4358dcf 100644 --- a/src/app/core/substance-form/structure/substance-form-structure-card.component.ts +++ b/src/app/core/substance-form/structure/substance-form-structure-card.component.ts @@ -113,7 +113,12 @@ export class SubstanceFormStructureCardComponent extends SubstanceFormBase imple this.menuLabelUpdate.emit('Idealized Structure'); // Display this message under the Ketcher Editor when Registering/Updating Polymer - this.disclaimer = "This is disclaimer"; + // if ketcherDisclaimer has string in the config.json file, display the disclaimer under the Ketcher Editor + if (this.configService && this.configService.configData && this.configService.configData.ketcherDisclaimer) { + if (this.configService.configData.ketcherDisclaimer) { + this.disclaimer = this.configService.configData.ketcherDisclaimer; + } + } const idealStructSubscription = this.substanceFormStructureService.substanceIdealizedStructure.subscribe(structure => { if (structure) { diff --git a/src/app/fda/config/config.json b/src/app/fda/config/config.json index 4f1889e88..7feb506d4 100644 --- a/src/app/fda/config/config.json +++ b/src/app/fda/config/config.json @@ -15,6 +15,7 @@ "useDataUrl": false, "showCrossEntitySearchDropdown": true, "disableJSDraw": false, + "ketcherDisclaimer": "", "restApiPrefix": "/ginas/app", "authenticateAs": { "apiUsername": null, From 8471f3ae4b604d1d3c1337515a89116e769648e8 Mon Sep 17 00:00:00 2001 From: Lihui Hu Date: Wed, 6 Aug 2025 14:52:25 -0400 Subject: [PATCH 070/408] add pdf download config --- src/app/fda/config/config.json | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/app/fda/config/config.json b/src/app/fda/config/config.json index 7feb506d4..78f59657a 100644 --- a/src/app/fda/config/config.json +++ b/src/app/fda/config/config.json @@ -42,6 +42,12 @@ } } }, + "enablePDFDownload":{ + "enablePDFDownload" : true, + "buttonName":"Print to PDF", + "companyName" : "", + "proprietaryNote":"" + }, "elementLabelDisplay": { "labels": { "substance_names_name": { From 7002b80e139fc80efc579b3c3a23d33b375564f7 Mon Sep 17 00:00:00 2001 From: Newatia Date: Thu, 7 Aug 2025 12:53:15 -0400 Subject: [PATCH 071/408] updated datepicker --- .../product-form/product-form.component.html | 44 ++++++++++++++--- .../product-form/product-form.component.scss | 4 +- .../product-form/product-form.component.ts | 28 ++++++++--- .../product-lot-form.component.html | 9 ++++ .../product-lot-form.component.ts | 48 ++++++++++++++----- 5 files changed, 106 insertions(+), 27 deletions(-) diff --git a/src/app/fda/product/product-form/product-form.component.html b/src/app/fda/product/product-form/product-form.component.html index 773abf1c5..9e8b54b58 100644 --- a/src/app/fda/product/product-form/product-form.component.html +++ b/src/app/fda/product/product-form/product-form.component.html @@ -170,22 +170,26 @@
    - + Effective Date (mm/dd/yyyy) - - - + + + - + End Date (mm/dd/yyyy) + + + + + + Start Marketing Date (mm/dd/yyyy) + + + + + + + End Marketing Date (mm/dd/yyyy) + + + + + +
    @@ -752,6 +772,15 @@
    + + + Effective Time (mm/dd/yyyy) + + + + + + + diff --git a/src/app/fda/product/product-form/product-form.component.scss b/src/app/fda/product/product-form/product-form.component.scss index 44ccdeb65..bb5a682f1 100644 --- a/src/app/fda/product/product-form/product-form.component.scss +++ b/src/app/fda/product/product-form/product-form.component.scss @@ -682,4 +682,6 @@ legend.border-two { mat-hint { color: var(--regular-red-color) !important; -} \ No newline at end of file +} + +/* Datepicker css */ diff --git a/src/app/fda/product/product-form/product-form.component.ts b/src/app/fda/product/product-form/product-form.component.ts index 4c6c56939..5602781b6 100644 --- a/src/app/fda/product/product-form/product-form.component.ts +++ b/src/app/fda/product/product-form/product-form.component.ts @@ -834,19 +834,33 @@ export class ProductFormComponent implements OnInit, AfterViewInit, OnDestroy { this.subscriptions.push(cvSubscription); } - dateChangeEffectiveDate(event: MatDatepickerInputEvent): void { + changeEffectiveDate(event: MatDatepickerInputEvent): void { if (event.value) { this.product.effectiveDate = moment(event.value).format('MM/DD/YYYY'); } } - onDateChange + changeEndDate(event: MatDatepickerInputEvent): void { + if (event.value) { + this.product.endDate = moment(event.value).format('MM/DD/YYYY'); + } + } + + changestartMarketingDate(event: MatDatepickerInputEvent, prodProvIndex: number, prodCompanyIndex: number): void { + if (event.value) { + this.product.productProvenances[prodProvIndex].productCompanies[prodCompanyIndex].startMarketingDate = moment(event.value).format('MM/DD/YYYY'); + } + } + + changeEndMarketingDate(event: MatDatepickerInputEvent, prodProvIndex: number, prodCompanyIndex: number): void { + if (event.value) { + this.product.productProvenances[prodProvIndex].productCompanies[prodCompanyIndex].endMarketingDate = moment(event.value).format('MM/DD/YYYY'); + } + } - openedChange(opened: boolean): void { - if (opened) { - this.increaseOverlayZindex(); - } else { - this.decreaseOverlayZindex(); + changeEffectiveTime(event: MatDatepickerInputEvent, prodProvIndex: number, prodDocIndex: number): void { + if (event.value) { + this.product.productProvenances[prodProvIndex].productDocumentations[prodDocIndex].effectiveTime = moment(event.value).format('MM/DD/YYYY'); } } diff --git a/src/app/fda/product/product-form/product-lot-form/product-lot-form.component.html b/src/app/fda/product/product-form/product-lot-form/product-lot-form.component.html index 3b36823a4..2cc2d5627 100644 --- a/src/app/fda/product/product-form/product-lot-form/product-lot-form.component.html +++ b/src/app/fda/product/product-form/product-lot-form/product-lot-form.component.html @@ -45,11 +45,20 @@ + + Expiry Date (mm/dd/yyyy) + + + + + + { @@ -131,6 +137,24 @@ export class ProductLotFormComponent implements OnInit { return isValid; } + changeExpiryDate(event: MatDatepickerInputEvent): void { + const selectedDate: Date | null = event.value; + + if (selectedDate) { + let dateFormattedStr = selectedDate.getMonth()+1 + '/' + selectedDate.getDate() + '/' + selectedDate.getFullYear(); + let dateObject: Date = new Date(dateFormattedStr); + this.productLot.expiryDate = dateObject; + } + } + + increaseOverlayZindex(): void { + this.overlayContainer.style.zIndex = '1002'; + } + + decreaseOverlayZindex(): void { + this.overlayContainer.style.zIndex = null; + } + isNumber(str: string): boolean { if ((str !== null) && (str !== '')) { const num = Number(str); From 8afc9d637f80118b4508881ae7753938c8542b5a Mon Sep 17 00:00:00 2001 From: Newatia Date: Thu, 7 Aug 2025 13:48:56 -0400 Subject: [PATCH 072/408] updated disclaimer --- src/app/core/config/config.model.ts | 2 +- .../structure-editor/structure-editor.component.html | 9 +++++---- .../structure-editor/structure-editor.component.scss | 12 ++++++++++-- .../substance-form-structure-card.component.ts | 10 +++++----- src/app/fda/config/config.json | 2 +- 5 files changed, 22 insertions(+), 13 deletions(-) diff --git a/src/app/core/config/config.model.ts b/src/app/core/config/config.model.ts index ff993e74d..d72c868cf 100644 --- a/src/app/core/config/config.model.ts +++ b/src/app/core/config/config.model.ts @@ -83,7 +83,7 @@ export interface Config { structureEditor?: 'ketcher' | 'jsdraw'; disableJSDraw?: boolean; disableKetcher?: boolean; - ketcherDisclaimer?: string; + polymerDisclaimer?: string; useApprovalAPI?: boolean; dummyWhoami?: Auth; enableStructureFeatures?: boolean; diff --git a/src/app/core/structure-editor/structure-editor.component.html b/src/app/core/structure-editor/structure-editor.component.html index 7b57a4f70..f5205c0b4 100644 --- a/src/app/core/structure-editor/structure-editor.component.html +++ b/src/app/core/structure-editor/structure-editor.component.html @@ -14,13 +14,14 @@
    -
    -
    +
    - DISCLAIMER: {{disclaimerMessage}} + DISCLAIMER: {{disclaimerMessage}}
    - Use copy dropdown menu on Ketcher editor and NOT ctrl + c to copy structure. + + Use copy dropdown menu on Ketcher editor and NOT ctrl + c to copy structure. +
    Load an image by pasting a copied image into the canvas with ctrl + v, or dragging a local image file. diff --git a/src/app/core/structure-editor/structure-editor.component.scss b/src/app/core/structure-editor/structure-editor.component.scss index 8747591dd..768262202 100644 --- a/src/app/core/structure-editor/structure-editor.component.scss +++ b/src/app/core/structure-editor/structure-editor.component.scss @@ -62,10 +62,18 @@ } -.marginright15px { - margin-left: 15px; +.marginright10px { + margin-left: 10px; } .textalignleft { text-align: left; +} + +.fontbold { + font-weight: 700; +} + +.fontsize12px { + font-size: 12px; } \ No newline at end of file diff --git a/src/app/core/substance-form/structure/substance-form-structure-card.component.ts b/src/app/core/substance-form/structure/substance-form-structure-card.component.ts index 5a4358dcf..26caeb15c 100644 --- a/src/app/core/substance-form/structure/substance-form-structure-card.component.ts +++ b/src/app/core/substance-form/structure/substance-form-structure-card.component.ts @@ -112,11 +112,11 @@ export class SubstanceFormStructureCardComponent extends SubstanceFormBase imple if (this.substanceType === 'polymer') { this.menuLabelUpdate.emit('Idealized Structure'); - // Display this message under the Ketcher Editor when Registering/Updating Polymer - // if ketcherDisclaimer has string in the config.json file, display the disclaimer under the Ketcher Editor - if (this.configService && this.configService.configData && this.configService.configData.ketcherDisclaimer) { - if (this.configService.configData.ketcherDisclaimer) { - this.disclaimer = this.configService.configData.ketcherDisclaimer; + // Display this message under JSDraw/ketcher Editor when Registering/Updating Polymer + // if polymerDisclaimer has string in the config.json file, display the disclaimer under the Structure Editor + if (this.configService && this.configService.configData && this.configService.configData.polymerDisclaimer) { + if (this.configService.configData.polymerDisclaimer) { + this.disclaimer = this.configService.configData.polymerDisclaimer; } } diff --git a/src/app/fda/config/config.json b/src/app/fda/config/config.json index 7feb506d4..31e04b4ed 100644 --- a/src/app/fda/config/config.json +++ b/src/app/fda/config/config.json @@ -15,7 +15,7 @@ "useDataUrl": false, "showCrossEntitySearchDropdown": true, "disableJSDraw": false, - "ketcherDisclaimer": "", + "polymerDisclaimer": "Please do not consider GSRS to be an expert system when registering polymer substances.", "restApiPrefix": "/ginas/app", "authenticateAs": { "apiUsername": null, From a490cdd2526a0945be66d30324e07dc0b6fe1876 Mon Sep 17 00:00:00 2001 From: Newatia Date: Mon, 11 Aug 2025 01:36:28 -0400 Subject: [PATCH 073/408] fixed more ketcher issues --- .../structure-editor.component.ts | 132 ++++--- src/app/core/structure/structure.service.ts | 10 +- .../advanced-selector-dialog.component.html | 2 +- .../advanced-selector-dialog.component.ts | 339 +++++++++--------- .../advanced-search.component.ts | 112 +++--- 5 files changed, 322 insertions(+), 273 deletions(-) diff --git a/src/app/core/structure-editor/structure-editor.component.ts b/src/app/core/structure-editor/structure-editor.component.ts index 23c14a938..fc287a58a 100644 --- a/src/app/core/structure-editor/structure-editor.component.ts +++ b/src/app/core/structure-editor/structure-editor.component.ts @@ -54,6 +54,7 @@ export class StructureEditorComponent implements OnInit, AfterViewInit, OnDestro firstload = true; calledFromComponent: string; disclaimerMessage: string; + pageKetcherIsOpen: string; private overlayContainer: HTMLElement; @ViewChild('structure_canvas', { static: false }) myCanvas: ElementRef; @@ -89,10 +90,16 @@ export class StructureEditorComponent implements OnInit, AfterViewInit, OnDestro this.destroyExistingKetcherInstance(); + this.structureService.updatePageKetcherIsOpen(''); this.structureService.updateReloadKetcher(true); } destroyExistingKetcherInstance(): boolean { + this.structureService.updatePageKetcherIsOpen(''); + + window.removeEventListener('drop', this.preventDrag); + window.removeEventListener('dragover', this.preventDrag); + window.removeEventListener('paste', this.checkPaste); // Delete existing Ketcher instance delete this.ketcher; delete window['ketcher']; @@ -190,6 +197,12 @@ export class StructureEditorComponent implements OnInit, AfterViewInit, OnDestro } ngOnInit() { + this.structureService.pageKetcherIsOpen$.subscribe((pageKetcherIsOpen) => { + if (pageKetcherIsOpen) { + this.pageKetcherIsOpen = pageKetcherIsOpen; + } + }); + this.structureService.reloadKetcher$.subscribe((reloadKetcher) => { if (reloadKetcher === true) { if (this.calledFromComponent && this.calledFromComponent === 'registerSubstance') { @@ -297,21 +310,21 @@ export class StructureEditorComponent implements OnInit, AfterViewInit, OnDestro if (!childElement) { const divElement = document.createElement("div"); divElement.setAttribute("id", "root"); - + divElement.style.height = '618px'; divElement.style.clear = 'both'; divElement.style.display = 'none'; - divElement.style.padding = '10px'; - + divElement.style.padding = '10px'; + // append child to parent parentElement.appendChild(divElement); window.addEventListener('click', this.listener); this.overlayContainer = this.overlayContainerService.getContainerElement(); this.editorSwitched.emit(this.structureEditor); - + this.ketcherReload(); - + } } } @@ -320,7 +333,9 @@ export class StructureEditorComponent implements OnInit, AfterViewInit, OnDestro ketcherReload() { this.firstload = true; - window.addEventListener('click', this.listener); + window.addEventListener('dragover', this.preventDrag); + window.addEventListener('drop', this.preventDrag); + window.addEventListener('paste', this.checkPaste); this.overlayContainer = this.overlayContainerService.getContainerElement(); if (isPlatformBrowser(this.platformId)) { @@ -375,7 +390,7 @@ export class StructureEditorComponent implements OnInit, AfterViewInit, OnDestro document.getElementById("root").style.display = ""; this.waitForKetcherFirstLoad(); this.firstload = false; - + } else if (this.firstload) { this.firstload = false; } @@ -464,12 +479,16 @@ export class StructureEditorComponent implements OnInit, AfterViewInit, OnDestro } async waitForKetcherFirstLoad(): Promise { + // Update global variable to let other instance know + if (this.pageKetcherIsOpen !== this.calledFromComponent) { + this.structureService.updatePageKetcherIsOpen(this.calledFromComponent); + } + await this.executeOnceNotNullOrUndefined(() => window['ketcher'], (obj) => { setTimeout(() => { this.ketcher = window['ketcher']; this.ketcherLoaded = true; document.getElementById("root").style.display = ""; - // this.editor = new EditorImplementation(this.ketcher, this.jsdraw, 'ketcher'); this.editor = new EditorImplementation(this.ketcher); this.editorOnLoad.emit(this.editor); this.editorSwitched.emit(this.structureEditor); @@ -606,57 +625,66 @@ export class StructureEditorComponent implements OnInit, AfterViewInit, OnDestro } catchPaste(event: ClipboardEvent): void { - const send: any = {}; - let valid = false; - const items = event.clipboardData.items; - for (let i = 0; i < items.length; i++) { - const blob = items[i].getAsFile(); - if (items[i].type.indexOf('image') !== -1) { - event.preventDefault(); - event.stopPropagation(); - this.canvasMessage = ''; - valid = true; - send.type = 'image'; - const reader = new FileReader(); - send.file = blob; - reader.readAsDataURL(blob); - const that = this; - reader.onloadend = () => { - setTimeout(() => { - const img = reader.result.toString(); - that.createImage(img); - }); - }; - } else if (items[i].type === 'text/plain') { - const text = event.clipboardData.getData('text/plain'); - if (text.indexOf(' { - - if (response.structure && response.structure.molfile) { - - this.editor.setMolecule(response.structure.molfile); - - this.loadedMolfile.emit(response.structure.molfile); - - if (response.structure.smiles === '') { - this.canvasMessage = 'empty or invalid structure pasted'; + valid = true; + send.type = 'image'; + const reader = new FileReader(); + send.file = blob; + reader.readAsDataURL(blob); + const that = this; + reader.onloadend = () => { + setTimeout(() => { + const img = reader.result.toString(); + that.createImage(img); + }); + }; + } else if (items[i].type === 'text/plain') { + const text = event.clipboardData.getData('text/plain'); + if (text.indexOf(' { + + if (response.structure && response.structure.molfile) { + + this.editor.setMolecule(response.structure.molfile); + + this.loadedMolfile.emit(response.structure.molfile); + + if (response.structure.smiles === '') { + this.canvasMessage = 'empty or invalid structure pasted'; + } + } else { + this.canvasMessage = 'Structure text not recognized'; } - } else { - this.canvasMessage = 'Structure text not recognized'; - } - this.loadingService.setLoading(false); + this.loadingService.setLoading(false); - }, error => { - this.loadingService.setLoading(false); - this.canvasMessage = 'empty or invalid structure pasted'; - }); + }, error => { + this.loadingService.setLoading(false); + this.canvasMessage = 'empty or invalid structure pasted'; + }); + } } } - } + } // if canPaste } cleanStructure() { diff --git a/src/app/core/structure/structure.service.ts b/src/app/core/structure/structure.service.ts index b333c0192..098b0d59c 100644 --- a/src/app/core/structure/structure.service.ts +++ b/src/app/core/structure/structure.service.ts @@ -20,14 +20,16 @@ export class StructureService { ) { } - private _isCalledFromRegisterSubstance = new BehaviorSubject(false); // Initial value - readonly isCalledFromRegisterSubstance$ = this._isCalledFromRegisterSubstance.asObservable(); // Expose as an Observable + private _pageKetcherIsOpen = new BehaviorSubject(''); // Initial value + readonly pageKetcherIsOpen$ = this._pageKetcherIsOpen.asObservable(); // Expose as an Observable private _reloadKetcher = new BehaviorSubject(false); // Initial value readonly reloadKetcher$ = this._reloadKetcher.asObservable(); // Expose as an Observable - isCalledFromRegisterSubstance(newValue: boolean) { - this._isCalledFromRegisterSubstance.next(newValue); // Update the value and notify subscribers + updatePageKetcherIsOpen(pageKetcherIsOpen: string) { + if (pageKetcherIsOpen) { + this._pageKetcherIsOpen.next(pageKetcherIsOpen); + } } updateReloadKetcher(uploadKetcher: boolean) { diff --git a/src/app/core/substance-selector/advanced-selector-dialog/advanced-selector-dialog.component.html b/src/app/core/substance-selector/advanced-selector-dialog/advanced-selector-dialog.component.html index 8e543931b..3d0aeefe0 100644 --- a/src/app/core/substance-selector/advanced-selector-dialog/advanced-selector-dialog.component.html +++ b/src/app/core/substance-selector/advanced-selector-dialog/advanced-selector-dialog.component.html @@ -11,7 +11,7 @@ (closed)="panelOpenState = false" [expanded]="panelOpenState">
    - +
    diff --git a/src/app/core/substance-selector/advanced-selector-dialog/advanced-selector-dialog.component.ts b/src/app/core/substance-selector/advanced-selector-dialog/advanced-selector-dialog.component.ts index 98066dc86..fc8a7e7e1 100644 --- a/src/app/core/substance-selector/advanced-selector-dialog/advanced-selector-dialog.component.ts +++ b/src/app/core/substance-selector/advanced-selector-dialog/advanced-selector-dialog.component.ts @@ -46,7 +46,7 @@ export class AdvancedSelectorDialogComponent implements OnInit { searchValue: string; nameSearched = false; structureSearched = false; - + calledFrom = 'substanceSelector'; loading = false; order = "default"; public sortValues = searchSortValues; @@ -55,27 +55,27 @@ export class AdvancedSelectorDialogComponent implements OnInit { current: string; lastTab: number; -smiles?: any; -searchType = 'substructure'; -_searchtype: string; -similarityCutoff?: number; -showSimilarityCutoff = false; -substances?: Array; -nameSubstances?: Array; -nameResponse: any; -response: any; + smiles?: any; + searchType = 'substructure'; + _searchtype: string; + similarityCutoff?: number; + showSimilarityCutoff = false; + substances?: Array; + nameSubstances?: Array; + nameResponse: any; + response: any; -panelOpenState = true; -private overlayContainer: HTMLElement; + panelOpenState = true; + private overlayContainer: HTMLElement; -private privateSearchTerm = ''; -private privateStructureSearchTerm?: string; -private privateSequenceSearchTerm?: string; -private privateSearchType?: string; -private privateSearchCutoff?: number; -private privateSearchSeqType?: string; -private privateSequenceSearchKey?: string; + private privateSearchTerm = ''; + private privateStructureSearchTerm?: string; + private privateSequenceSearchTerm?: string; + private privateSearchType?: string; + private privateSearchCutoff?: number; + private privateSearchSeqType?: string; + private privateSequenceSearchKey?: string; constructor( @@ -92,9 +92,9 @@ private privateSequenceSearchKey?: string; @Inject(MAT_DIALOG_DATA) public data: any ) { this.dat = data; - + } - + get standardized(): boolean { @@ -111,29 +111,29 @@ private privateSequenceSearchKey?: string; if (this.configService.configData && this.configService.configData.gsrsHomeBaseUrl) { url = this.configService.configData.gsrsHomeBaseUrl + '/substances/register/chemical' + '?importStructure=' + encodeURIComponent(smiles); } else { - + const baseUrl = window.location.href.replace(this.router.url, ''); url = baseUrl + this.router.serializeUrl( this.router.createUrlTree(['/substances/register/chemical'], { queryParams: navigationExtras.queryParams}) ); } - + window.open(url, '_blank'); } close() { - this.dialogRef.close(); + this.dialogRef.close(); } standardize(standard: string): void { let mol = '' - this.editor.getMolfile().pipe(take(1)).subscribe(response => { + this.editor.getMolfile().pipe(take(1)).subscribe(response => { mol = response; - this.structureService.interpretStructure(mol, '', standard).subscribe((response: InterpretStructureResponse) => { - if (response && response.structure && response.structure.molfile) { - this.editor.setMolecule(response.structure.molfile); - } + this.structureService.interpretStructure(mol, '', standard).subscribe((response: InterpretStructureResponse) => { + if (response && response.structure && response.structure.molfile) { + this.editor.setMolecule(response.structure.molfile); + } }, () => {}); }); } @@ -159,28 +159,28 @@ private privateSequenceSearchKey?: string; this.searchSubstances(null, null, 'name'); } - ngOnInit(): void { + ngOnInit(): void { this.overlayContainer = this.overlayContainerService.getContainerElement(); - + if (this.privateTerm.simplifiedStructure) { this.privateTerm.simpleSrc = this.CVService.getStructureUrl(this.privateTerm.simplifiedStructure); - } - if (this.privateTerm.fragmentStructure) { - this.privateTerm.fragmentSrc = this.CVService.getStructureUrl(this.privateTerm.fragmentStructure); - } - this.overlayContainer = this.overlayContainerService.getContainerElement(); + } + if (this.privateTerm.fragmentStructure) { + this.privateTerm.fragmentSrc = this.CVService.getStructureUrl(this.privateTerm.fragmentStructure); + } + this.overlayContainer = this.overlayContainerService.getContainerElement(); if(this.dat && this.dat.uuid && this.data.tab !== 1) { - this.structureService.getMolfile(this.dat.uuid); - } + this.structureService.getMolfile(this.dat.uuid); + } if(this.dat && this.dat.name) { - this.searchValue = this.dat.name; - } - this.activeTab = this.data.tab; - setTimeout(() => { + this.searchValue = this.dat.name; + } this.activeTab = this.data.tab; - }, 10); + setTimeout(() => { + this.activeTab = this.data.tab; + }, 10); } @@ -190,44 +190,44 @@ private privateSequenceSearchKey?: string; editorOnLoad(editor: Editor): void { this.overlayContainer.style.zIndex = '1003'; - - this.overlayContainer.style.zIndex = '10003'; + + this.overlayContainer.style.zIndex = '10003'; this.loadingService.setLoading(false); this.editor = editor; if(this.dat && this.dat.uuid) { this.structureService.getMolfile(this.data.uuid).subscribe( response => { - this.editor.setMolecule(response); - this.overlayContainer.style.zIndex = '1003'; - - this.overlayContainer.style.zIndex = '10003'; - - }); - } else if ( - this.dat && this.dat.molfile - ) { - this.editor.setMolecule(this.dat.molfile); + this.editor.setMolecule(response); + this.overlayContainer.style.zIndex = '1003'; + this.overlayContainer.style.zIndex = '10003'; - } - + + }); + } else if ( + this.dat && this.dat.molfile + ) { + this.editor.setMolecule(this.dat.molfile); + this.overlayContainer.style.zIndex = '10003'; + } + setTimeout(() => { // re-adjust z-index after editor messes it up (to adjust relative index for periodic table, right click) this.overlayContainer.style.zIndex = '1003'; - + this.overlayContainer.style.zIndex = '10003'; - }, 100); + }, 100); } search(): void { let mol = ''; this.editor.getMolfile().pipe(take(1)).subscribe(response => { mol = response; - this.structureService.interpretStructure(mol).subscribe((response: InterpretStructureResponse) => { - this.smiles = response.structure.smiles; + this.structureService.interpretStructure(mol).subscribe((response: InterpretStructureResponse) => { + this.smiles = response.structure.smiles; this.response = response.structure.id; this.searchSubstances(response.structure.id, response.structure.smiles); }, () => {}); - }); -} + }); + } nameResolved(molfile: string): void { @@ -236,7 +236,7 @@ private privateSequenceSearchKey?: string; updateType(event: any) { this.searchType = event.value; - + this.privateSearchType = event.value; if (this.searchType === 'similarity') { @@ -255,32 +255,32 @@ private privateSequenceSearchKey?: string; const navigationExtras: NavigationExtras = { queryParams: {} }; - const navigationExtras2: NavigationExtras = { + const navigationExtras2: NavigationExtras = { queryParams: {} }; if (type === 'structure') { navigationExtras.queryParams['structure_search'] = this.privateStructureSearchTerm || null; navigationExtras.queryParams['type'] = this.searchType || null; - + navigationExtras2.queryParams['structure'] = this.privateStructureSearchTerm || null; navigationExtras2.queryParams['type'] = this.searchType || null; navString += '?structure_search=' + navigationExtras.queryParams['structure_search'] + '&type=' + navigationExtras.queryParams['type']; - + if (this.searchType === 'similarity') { navigationExtras.queryParams['cutoff'] = this.similarityCutoff || 0; navString += '&cutoff=' + navigationExtras.queryParams['cutoff'] ; } - + if (smiles != null) { navigationExtras.queryParams['smiles'] = smiles; } - - - - + + + + } else { navigationExtras.queryParams['search'] = this.searchValue; navString += '?search=' + navigationExtras.queryParams['search']; @@ -300,7 +300,7 @@ private privateSequenceSearchKey?: string; queryParams: navigationExtras.queryParams}) ); } - + window.open(url, '_blank'); } @@ -312,7 +312,7 @@ private privateSequenceSearchKey?: string; if (structureSearchTerm){ this.privateStructureSearchTerm = structureSearchTerm || null; this.privateSearchType = this.searchType || 'substructure'; - + if (this.searchType === 'similarity') { this.privateSearchCutoff = this.similarityCutoff || 0; } @@ -326,122 +326,133 @@ private privateSequenceSearchKey?: string; let sort = null; if(type && type === 'name') { sort = this.order; - - } - this.loadingService.setLoading(true); - this.loading = true; - const subscription = this.substanceService.getSubstancesSummaries({ - searchTerm: this.privateSearchTerm, - structureSearchTerm: this.privateStructureSearchTerm, - sequenceSearchTerm: this.privateSequenceSearchTerm, - cutoff: this.privateSearchCutoff, - type: this.privateSearchType, - seqType: this.privateSearchSeqType, - order: sort, - pageSize: size, - facets: this.privateFacetParams, - skip: index, - sequenceSearchKey: this.privateSequenceSearchKey, - deprecated: false - }) - .subscribe(pagingResponse => { - + + } + this.loadingService.setLoading(true); + this.loading = true; + const subscription = this.substanceService.getSubstancesSummaries({ + searchTerm: this.privateSearchTerm, + structureSearchTerm: this.privateStructureSearchTerm, + sequenceSearchTerm: this.privateSequenceSearchTerm, + cutoff: this.privateSearchCutoff, + type: this.privateSearchType, + seqType: this.privateSearchSeqType, + order: sort, + pageSize: size, + facets: this.privateFacetParams, + skip: index, + sequenceSearchKey: this.privateSequenceSearchKey, + deprecated: false + }) + .subscribe(pagingResponse => { + if(type && type === 'name') { - this.nameSubstances = (pagingResponse && pagingResponse.content) ? pagingResponse.content : []; - this.nameTotalSubstances = pagingResponse.total; - } else { - this.substances = (pagingResponse && pagingResponse.content) ? pagingResponse.content : []; - this.totalSubstances = pagingResponse.total; - if (this.totalSubstances > 0) { - this.panelOpenState = false; - } + this.nameSubstances = (pagingResponse && pagingResponse.content) ? pagingResponse.content : []; + this.nameTotalSubstances = pagingResponse.total; + } else { + this.substances = (pagingResponse && pagingResponse.content) ? pagingResponse.content : []; + this.totalSubstances = pagingResponse.total; + if (this.totalSubstances > 0) { + this.panelOpenState = false; } + } + + if (pagingResponse.total % this.pageSize === 0) { + this.lastPage = (pagingResponse.total / this.pageSize); + } else { + this.lastPage = Math.floor(pagingResponse.total / this.pageSize + 1); + } - if (pagingResponse.total % this.pageSize === 0) { - this.lastPage = (pagingResponse.total / this.pageSize); - } else { - this.lastPage = Math.floor(pagingResponse.total / this.pageSize + 1); - } - + this.overlayContainer.style.zIndex = '1003'; + + this.overlayContainer.style.zIndex = '10003'; + this.loadingService.setLoading(false); + + this.overlayContainer.style.zIndex = '10003'; + setTimeout(() => { + // re-adjust z-index after editor messes it up this.overlayContainer.style.zIndex = '1003'; - - this.overlayContainer.style.zIndex = '10003'; - this.loadingService.setLoading(false); - - this.overlayContainer.style.zIndex = '10003'; - setTimeout(() => { - // re-adjust z-index after editor messes it up - this.overlayContainer.style.zIndex = '1003'; - - this.overlayContainer.style.zIndex = '10003'; - this.loading = false; - }); + this.overlayContainer.style.zIndex = '10003'; - }, error => { this.loading = false; - this.loadingService.setLoading(false); - console.log("error getting name in function searchSubstances()"); }); - } - openStructureImportDialog(): void { - const dialogRef = this.dialog.open(StructureImportComponent, { - height: 'auto', - width: '650px', - data: {} - }); - // this.overlayContainer.style.zIndex = '1002'; - - dialogRef.afterClosed().subscribe((structurePostResponse?: InterpretStructureResponse) => { - setTimeout(() => { - this.overlayContainer.style.zIndex = '1003'; - this.overlayContainer.style.zIndex = '10003'; - }); - if (structurePostResponse && structurePostResponse.structure && structurePostResponse.structure.molfile) { + }, error => { + this.loading = false; + this.loadingService.setLoading(false); + console.log("error getting name in function searchSubstances()"); + }); + } + + openStructureImportDialog(): void { + const dialogRef = this.dialog.open(StructureImportComponent, { + height: 'auto', + width: '650px', + data: {} + }); + // this.overlayContainer.style.zIndex = '1002'; + + dialogRef.afterClosed().subscribe((structurePostResponse?: InterpretStructureResponse) => { + setTimeout(() => { + this.overlayContainer.style.zIndex = '1003'; + this.overlayContainer.style.zIndex = '10003'; + }); + if (structurePostResponse && structurePostResponse.structure && structurePostResponse.structure.molfile) { setTimeout(()=>{ - this.editor.setMolecule(structurePostResponse.structure.molfile); - }) - } - }, () => { - setTimeout(() => { - this.overlayContainer.style.zIndex = '1003'; - this.overlayContainer.style.zIndex = '10003'; - }); - }); + this.editor.setMolecule(structurePostResponse.structure.molfile); + }) } - - openStructureExportDialog(): void { + }, () => { + setTimeout(() => { + this.overlayContainer.style.zIndex = '1003'; + this.overlayContainer.style.zIndex = '10003'; + }); + }); + } + + openStructureExportDialog(): void { + let mol = ''; + let smiles = ''; + this.editor.getMolfile().pipe(take(1)).subscribe(response => { + mol = response; + + this.structureService.interpretStructure(mol).subscribe((response: InterpretStructureResponse) => { + smiles = response.structure.smiles; + const dialogRef = this.dialog.open(StructureExportComponent, { height: 'auto', width: '650px', data: { - molfile:'',// this.editor.getMolfile(), - smiles: ''// this.editor.getSmiles() + molfile: mol, // '' or this.editor.getMolfile(), + smiles: smiles // '' or this.editor.getSmiles() } }); - // this.overlayContainer.style.zIndex = '1002'; - + dialogRef.afterClosed().subscribe(() => { setTimeout(() => { this.overlayContainer.style.zIndex = '1003'; this.overlayContainer.style.zIndex = '10003'; - }); + }); }, () => { setTimeout(() => { this.overlayContainer.style.zIndex = '1003'; this.overlayContainer.style.zIndex = '10003'; - }); + }); }); - } + }); // getSmiles() + + }); // getMolfile() + + } checkImg(term: any) { term.fragmentSrc = this.CVService.getStructureUrlFragment(term.fragmentStructure); term.simpleSrc = this.CVService.getStructureUrlFragment(term.simplifiedStructure); - } + openImageModal(substance: SubstanceDetail): void { let data: any; @@ -470,7 +481,7 @@ private privateSequenceSearchKey?: string; } - + const dialogRef = this.dialog.open(StructureImageModalComponent, { width: '650px', panelClass: 'structure-image-panel', @@ -492,7 +503,7 @@ private privateSequenceSearchKey?: string; } }, 150); } - + } if (response && response === 'select') { this.selectSubstance(substance); @@ -516,15 +527,15 @@ private privateSequenceSearchKey?: string; if (type && type === 'name') { this.namePageSize = pageEvent.pageSize; this.namePageIndex = pageEvent.pageIndex; - this.searchSubstances(null, null, 'name'); - + this.searchSubstances(null, null, 'name'); + } else { this.pageSize = pageEvent.pageSize; this.pageIndex = pageEvent.pageIndex; - this.searchSubstances(this.response); - + this.searchSubstances(this.response); + } - + } } diff --git a/src/app/fda/advanced-search/advanced-search.component.ts b/src/app/fda/advanced-search/advanced-search.component.ts index 2c27f92df..9237ecfad 100644 --- a/src/app/fda/advanced-search/advanced-search.component.ts +++ b/src/app/fda/advanced-search/advanced-search.component.ts @@ -803,52 +803,52 @@ export class AdvancedSearchComponent implements OnInit, OnDestroy { let mol = ''; this.editor.getMolfile().pipe(take(1)).subscribe(response => { mol = response; - if (mol && mol.length > 72) { - this.structureService.interpretStructure(mol).subscribe((response: InterpretStructureResponse) => { - const eventLabel = !environment.isAnalyticsPrivate && response.structure.smiles || 'structure search term'; - // this.gaService.sendEvent('structureSearch', 'button:search', eventLabel); + if (mol && mol.length > 72) { + this.structureService.interpretStructure(mol).subscribe((response: InterpretStructureResponse) => { + const eventLabel = !environment.isAnalyticsPrivate && response.structure.smiles || 'structure search term'; + // this.gaService.sendEvent('structureSearch', 'button:search', eventLabel); - const navigationExtrasStructure: NavigationExtras = { - queryParams: {} - }; + const navigationExtrasStructure: NavigationExtras = { + queryParams: {} + }; - const structureSearchTerm = response.structure.id; - const smiles = response.structure.smiles; + const structureSearchTerm = response.structure.id; + const smiles = response.structure.smiles; - navigationExtras.queryParams['structure_search'] = structureSearchTerm || null; - navigationExtras.queryParams['type'] = this.searchType || null; + navigationExtras.queryParams['structure_search'] = structureSearchTerm || null; + navigationExtras.queryParams['type'] = this.searchType || null; - navigationExtras2.queryParams['structure'] = structureSearchTerm; - navigationExtras2.queryParams['type'] = this.searchType || null; + navigationExtras2.queryParams['structure'] = structureSearchTerm; + navigationExtras2.queryParams['type'] = this.searchType || null; - if (this.searchType === 'similarity') { - navigationExtras.queryParams['cutoff'] = this.similarityCutoff || 0; - navigationExtras2.queryParams['cutoff'] = this.similarityCutoff || 0; - } + if (this.searchType === 'similarity') { + navigationExtras.queryParams['cutoff'] = this.similarityCutoff || 0; + navigationExtras2.queryParams['cutoff'] = this.similarityCutoff || 0; + } - if (smiles != null) { - navigationExtras.queryParams['smiles'] = smiles; - } + if (smiles != null) { + navigationExtras.queryParams['smiles'] = smiles; + } - // this is a test of the push state needed - // to keep the back button working as desired - window.history.pushState({}, 'Structure Search', '/structure-search' - + '?structure=' + navigationExtras2.queryParams['structure'] - + '&type=' + navigationExtras2.queryParams['type'] - + '&cutoff=' + navigationExtras2.queryParams['cutoff']); + // this is a test of the push state needed + // to keep the back button working as desired + window.history.pushState({}, 'Structure Search', '/structure-search' + + '?structure=' + navigationExtras2.queryParams['structure'] + + '&type=' + navigationExtras2.queryParams['type'] + + '&cutoff=' + navigationExtras2.queryParams['cutoff']); - this.router.navigate(['/browse-substance'], navigationExtras); - }, () => { }); + this.router.navigate(['/browse-substance'], navigationExtras); + }, () => { }); - } - /********* STRUCTURE QUERY **********/ + } + /********* STRUCTURE QUERY **********/ - // If no structure search, do this - else { - this.router.navigate(['/browse-substance'], navigationExtras); - // } - } - }); + // If no structure search, do this + else { + this.router.navigate(['/browse-substance'], navigationExtras); + // } + } + }); } else if (this.category === 'Application') { this.router.navigate(['/browse-applications'], navigationExtras); @@ -937,24 +937,32 @@ export class AdvancedSearchComponent implements OnInit, OnDestroy { openStructureExportDialog(): void { let mol = ''; + let smiles = ''; + this.editor.getMolfile().pipe(take(1)).subscribe(response => { mol = response; - this.gaService.sendEvent('structureSearch', 'button:export', 'export structure'); - const dialogRef = this.dialog.open(StructureExportComponent, { - height: 'auto', - width: '650px', - data: { - molfile: mol, - smiles: '' //this.editor.getSmiles() - } - }); - this.overlayContainer.style.zIndex = '1002'; - dialogRef.afterClosed().subscribe(() => { - this.overlayContainer.style.zIndex = null; - }, () => { - this.overlayContainer.style.zIndex = null; - }); - }); + + this.structureService.interpretStructure(mol).subscribe((response: InterpretStructureResponse) => { + smiles = response.structure.smiles; + + this.gaService.sendEvent('structureSearch', 'button:export', 'export structure'); + const dialogRef = this.dialog.open(StructureExportComponent, { + height: 'auto', + width: '650px', + data: { + molfile: mol, + smiles: smiles + } + }); + this.overlayContainer.style.zIndex = '1002'; + dialogRef.afterClosed().subscribe(() => { + this.overlayContainer.style.zIndex = null; + }, () => { + this.overlayContainer.style.zIndex = null; + }); + + }); // getSmiles + }); // getMolFile } searchCutoffChanged(event): void { From 71311e4d09373d0d3cc8eb9fbbcc7a367dc62adc Mon Sep 17 00:00:00 2001 From: Newatia Date: Tue, 12 Aug 2025 12:05:25 -0400 Subject: [PATCH 074/408] updated ketcher copy paste text --- src/app/core/structure-editor/structure-editor.component.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/core/structure-editor/structure-editor.component.html b/src/app/core/structure-editor/structure-editor.component.html index f5205c0b4..a8c218f7e 100644 --- a/src/app/core/structure-editor/structure-editor.component.html +++ b/src/app/core/structure-editor/structure-editor.component.html @@ -20,7 +20,7 @@ DISCLAIMER: {{disclaimerMessage}}
    - Use copy dropdown menu on Ketcher editor and NOT ctrl + c to copy structure. + Use blue Export and Import buttons to copy and paste structure.
    From e4baa10499b4ac62cc7b5ad707c48bba25428742 Mon Sep 17 00:00:00 2001 From: Jaroslav Iha Date: Wed, 13 Aug 2025 12:14:39 +0200 Subject: [PATCH 075/408] update JSDraw license --- src/app/core/assets/jsdraw/Scilligence.JSDraw2.Pro.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/app/core/assets/jsdraw/Scilligence.JSDraw2.Pro.js b/src/app/core/assets/jsdraw/Scilligence.JSDraw2.Pro.js index b007bf5b5..a3fad8d9d 100644 --- a/src/app/core/assets/jsdraw/Scilligence.JSDraw2.Pro.js +++ b/src/app/core/assets/jsdraw/Scilligence.JSDraw2.Pro.js @@ -25,8 +25,8 @@ JSDraw2.password = { encrypt: true, key: null, iv: null }; // Place the license code below // Licensed to: FDA // Product: JSDraw -// Expiration Date: 2025-Jul-30 -JSDraw2.licensecode='405562538916781761723242424242424131213141512181'; +// Expiration Date: 2026-Jul-30 +JSDraw2.licensecode='405562537916781761723242424242424131213141512181'; ////////////////////////////////////////////////////////////////////////////////// From 1249ca1d6d9cd5c236f5ce7e8f75b1fb27353b0c Mon Sep 17 00:00:00 2001 From: Mitch Miller Date: Wed, 13 Aug 2025 17:58:54 -0400 Subject: [PATCH 076/408] changed defiant to jsonpath within definition-switch-dialog.component --- .../definition-switch-dialog.component.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/app/core/substance-form/definition-switch-dialog/definition-switch-dialog.component.ts b/src/app/core/substance-form/definition-switch-dialog/definition-switch-dialog.component.ts index ee0dcb287..8fd024b9f 100644 --- a/src/app/core/substance-form/definition-switch-dialog/definition-switch-dialog.component.ts +++ b/src/app/core/substance-form/definition-switch-dialog/definition-switch-dialog.component.ts @@ -4,9 +4,9 @@ import { SubstanceDetail, SubstanceService } from '@gsrs-core/substance'; import { SubstanceFormService } from '@gsrs-core/substance-form/substance-form.service'; import { DomSanitizer } from '@angular/platform-browser'; import * as _ from 'lodash'; -import * as defiant from '../../../../../node_modules/defiant.js/dist/defiant.min.js'; import { LoadingService } from '@gsrs-core/loading/index'; import { UtilsService } from '@gsrs-core/utils/index'; +import jp from 'jsonpath'; @Component( { selector: 'app-definition-switch-dialog', @@ -168,10 +168,10 @@ export class DefinitionSwitchDialogComponent implements OnInit { } this.oldPrime.references.push(depRef); - this.test1 = defiant.json.search(this.oldPrime, '//*[references]'); + this.test1 = jp.query(this.oldPrime, '$..[?(@.references)]'); this.substanceService.getSubstanceDetails(uuid).subscribe(d => { - this.test2 = defiant.json.search(d, '//*[references]'); - + this.test2 = jp.query(d, '$..[?(@.references)]'); + this.oldAlt = _.cloneDeep(d); if (!this.fieldGetter[this.oldAlt.substanceClass]) { this.text = 'The selected alternative is incompatible with the definition switch function'; @@ -275,7 +275,7 @@ export class DefinitionSwitchDialogComponent implements OnInit { } }); alt.substanceClass = this.sub.substanceClass; - const altReferences = defiant.json.search(alt, '//*[references]'); + const altReferences = jp.query(alt, '$..[?(@.references)]'); altReferences.forEach(e => { }); const objectsA = altReferences.filter(e => { @@ -344,7 +344,7 @@ export class DefinitionSwitchDialogComponent implements OnInit { newSub[x] = this.oldAlt[x]; } }); - const subReferences = defiant.json.search(newSub, '//*[references]'); + const subReferences = jp.query(newSub, '$..[?(@.references)]'); const objectsA = subReferences.filter(h => { if (this.isObject(h)) { From d53920860f60bdacd081b7eb8891c3b6a8ea8955 Mon Sep 17 00:00:00 2001 From: Newatia Date: Mon, 18 Aug 2025 17:11:08 -0400 Subject: [PATCH 077/408] update product form --- .../product-lot-form/product-lot-form.component.html | 7 +++++++ .../product-lot-form/product-lot-form.component.ts | 12 ++++++++++++ 2 files changed, 19 insertions(+) diff --git a/src/app/fda/product/product-form/product-lot-form/product-lot-form.component.html b/src/app/fda/product/product-form/product-lot-form/product-lot-form.component.html index 2cc2d5627..4b5c2ee08 100644 --- a/src/app/fda/product/product-form/product-lot-form/product-lot-form.component.html +++ b/src/app/fda/product/product-form/product-lot-form/product-lot-form.component.html @@ -52,6 +52,13 @@ + + Manufacture Date (mm/dd/yyyy) + + + + +

    - Select a file to Import Assays Only:   + Select an Excel file to Import Assays Only into Database:   - + + -
    +
    - +
    @@ -53,8 +54,8 @@ into Database
    +
    - +
    {{submitMessage}}

    - +
    Validation Results
    @@ -94,38 +96,52 @@ -
    - +
    +
    - - - + + + + + - + + + - +
    Record RowExternal Assay SourceExternal Assay ID#Assay Set *External Assay Source *External Assay ID *Assay Target Name * Validation Error MessageIs ValidValidation Pass/Fail
    {{(msg.indexRecord+1)}} +
    +
    + {{(indexAssaySet+1)}}. {{set.assaySet}} +
    +
    {{msg.externalAssaySource}} {{msg.externalAssayId}}{{msg.targetName}}
    -
    -
    - {{message.messageType}}
    -
    {{message.message}}
    {{link.text}}
    - +
    +
    + • {{message.message}}
    {{msg.valid}} + +
    +
    check
    +
    Passed
    +
    + + +
    +
    clear
    +
    Failed
    +
    +
    @@ -135,22 +151,27 @@ +
    + -
    Records Saved ({{importSaveMessageArray.length}})
    +
    Assay records from Excel file saved into the database ({{importSaveMessageArray.length}})

    - - - +
    Record Row
    + + + - + - + + @@ -170,22 +191,27 @@ -
    +
    -

    - - Total Records ready for import into the database:  - {{importDataList.length}} - +
    +
    Total Assay Records ready for import into the database from Excel + file:  + {{importDataList.length}} +
    +
    + * = Required Field +
    +
    -

    -
    Excel File Record RowAssay ID External Assay Source External Assay ID Saved Into The DatabaseSaved Assay IdSaved Assay Details
    {{(msg.indexRecord+1)}}{{msg.assayId}} {{msg.externalAssaySource}} {{msg.externalAssayId}} {{msg.saved}}
    +
    +
    - - - + + + + @@ -197,7 +223,7 @@ - + @@ -206,15 +232,18 @@ + - + @@ -237,6 +266,12 @@ +
    #Assay SetExternal Assay SourceExternal Assay IDAssay Set *Assay IDExternal Assay Source *External Assay ID * External Assay Reference URL Assay Title Assay FormatPresention Type Presentation Public DomainAssay Target NameAssay Target Name * Assay Target Name Approval ID Target Species Human Homolog Target NameLigand/Substrate Approval ID Standard Ligand/Substrate Concentration Standard Ligand/Substrate Concentration UnitsAnalytes
    {{(index+1)}} -
    - {{set.assaySet}} +
    +
    +
    + {{(indexAssaySet+1)}}. {{set.assaySet}}
    {{assay.assayId}} {{assay.externalAssaySource}} {{assay.externalAssayId}} {{assay.externalAssayReferenceUrl}}{{assay.ligandSubstrateApprovalId}} {{assay.standardLigandSubstrateConcentration}} {{assay.standardLigandSubstrateConcentrationUnits}} +
    +
    + {{(indexAnalytes+1)}}. {{a.analyte}} +
    +
    @@ -246,19 +281,19 @@

    -
    - - - - -
    -

    -
    + +
    + + + + +
    +

    +
    diff --git a/src/app/fda/invitro-pharmacology/invitro-pharmacology-assay-data-import/invitro-pharmacology-assay-data-import.component.scss b/src/app/fda/invitro-pharmacology/invitro-pharmacology-assay-data-import/invitro-pharmacology-assay-data-import.component.scss index 5c26f6ad4..9640d63e4 100644 --- a/src/app/fda/invitro-pharmacology/invitro-pharmacology-assay-data-import/invitro-pharmacology-assay-data-import.component.scss +++ b/src/app/fda/invitro-pharmacology/invitro-pharmacology-assay-data-import/invitro-pharmacology-assay-data-import.component.scss @@ -15,6 +15,7 @@ overflow-x: auto; overflow-y: hidden; padding-top: 110px; + padding-right: 70px; } .scrollable-container { @@ -52,10 +53,6 @@ display: flex; } -.divflex { - display: flex; -} - .divcenter { max-width: 90%; margin: 0 auto; @@ -313,6 +310,14 @@ margin-left: 30px; } +.marginleft180px { + margin-left: 180px; +} + +.marginright20px { + margin-right: 20px; +} + .padtop5px { padding-top: 5px; } @@ -329,6 +334,18 @@ padding-top: 17px; } +.padright40px { + padding-right: 40px; +} + +.backgroundcolorgreen { + background-color: rgb(87, 205, 87); +} + +.backgroundcolorred { + background-color: rgb(255, 63, 63); +} + .bordergray { border: 1px solid var(--regular-grey-color); } @@ -353,6 +370,10 @@ width: 32%; } +.widthmax100percent { + max-width: 100%; +} + .colorgray { color: var(--regular-grey-color); } @@ -365,6 +386,11 @@ color: var(--regular-green-color); } +.colorblue { + /* BLUE CODE Hex: #007CBA */ + color: #007CBA; +} + .font11px { font-size: 11px; } @@ -389,6 +415,10 @@ font-size: 18px; } +.font20px { + font-size: 20px; +} + .textalignright { text-align: right; } @@ -516,8 +546,11 @@ table.tableStyle { border: 1px solid var(--secondary-blue-color); background-color: var(--table-bg-color); width: 100%; + max-width: 100%; text-align: left; border-collapse: collapse; + margin: 0 auto; + padding-right: 60px; } table.tableStyle td, table.tableStyle th { @@ -526,7 +559,7 @@ table.tableStyle td, table.tableStyle th { } table.tableStyle tbody td { - font-size: 16px; + font-size: 12px; vertical-align: top; padding: 10px 10px; } diff --git a/src/app/fda/invitro-pharmacology/invitro-pharmacology-assay-data-import/invitro-pharmacology-assay-data-import.component.ts b/src/app/fda/invitro-pharmacology/invitro-pharmacology-assay-data-import/invitro-pharmacology-assay-data-import.component.ts index 339071ee5..564c322a3 100644 --- a/src/app/fda/invitro-pharmacology/invitro-pharmacology-assay-data-import/invitro-pharmacology-assay-data-import.component.ts +++ b/src/app/fda/invitro-pharmacology/invitro-pharmacology-assay-data-import/invitro-pharmacology-assay-data-import.component.ts @@ -1,4 +1,5 @@ -import { Component, OnInit, OnDestroy } from '@angular/core'; +import { Component, OnInit, OnDestroy, ViewChild, TemplateRef } from '@angular/core'; +import { HttpErrorResponse } from '@angular/common/http'; import { ActivatedRoute, Router } from '@angular/router'; import { DomSanitizer, SafeUrl } from '@angular/platform-browser'; import { FormBuilder } from '@angular/forms'; @@ -28,7 +29,7 @@ import { ConfirmDialogComponent } from '../../confirm-dialog/confirm-dialog.comp /* Invitro Pharmacology Imports */ import { InvitroPharmacologyService } from '../service/invitro-pharmacology.service' -import { InvitroAssayInformation, InvitroAssaySet, ValidationMessage } from '../model/invitro-pharmacology.model'; +import { InvitroAssayInformation, InvitroAssaySet, InvitroAssayAnalyte, ValidationMessage } from '../model/invitro-pharmacology.model'; @Component({ selector: 'app-invitro-pharmacology-assay-data-import', @@ -37,11 +38,13 @@ import { InvitroAssayInformation, InvitroAssaySet, ValidationMessage } from '../ }) export class InvitroPharmacologyAssayDataImportComponent implements OnInit { - private TARGET_NAME = "TARGET_NAME"; - private HUMAN_HOMOLOG_TARGET = "HUMAN_HOMOLOG_TARGET"; - private LIGAND_SUBSTRATE = "LIGAND_SUBSTRATE"; - private ANALYTE = "ANALYTE"; + @ViewChild('saveTemplate') saveTemplate: TemplateRef; + private TARGET_NAME = "Assay Target Name"; + private HUMAN_HOMOLOG_TARGET = "Human Homolog Target"; + private LIGAND_SUBSTRATE = "Ligand/Substrate"; + + private overlayContainer: HTMLElement; substanceKeyTypeForInvitroPharmacologyConfig = null; private subscriptions: Array = []; @@ -56,14 +59,18 @@ export class InvitroPharmacologyAssayDataImportComponent implements OnInit { importValidateMessageArray: Array = []; importSaveMessageArray: Array = []; - willDownload = false; - isAllRecordValid: boolean; - isAllRecordValidated = false; isAllRecordSaved = false; - isLoading = false; + isAdmin = false; + + targetNameCheckCompleted = false; + humanHomologCheckCompleted = false; + ligandCheckCompleted = false; + + message = ''; + submitMessage = ''; errorMessage: string; serverError: boolean; @@ -71,9 +78,8 @@ export class InvitroPharmacologyAssayDataImportComponent implements OnInit { showSubmissionMessages = false; validationResult = false; validationMessages: Array = []; - message = ''; - submitMessage = ''; - isAdmin = false; + + totalRecordSavedInDatabase = 0; constructor( private activatedRoute: ActivatedRoute, @@ -91,13 +97,15 @@ export class InvitroPharmacologyAssayDataImportComponent implements OnInit { ) { } ngOnInit(): void { - this.titleService.setTitle("IVP Import Assay Data"); - // Check if user has either Admin or Updater role this.authService.hasAnyRolesAsync('DataEntry', 'SuperDataEntry', 'Admin').subscribe(response => { this.isAdmin = response; }); + this.overlayContainer = this.overlayContainerService.getContainerElement(); + + this.titleService.setTitle("IVP Import Assay Data"); + // Get Invitro Pharmacology Substance Key Type from the configuration file this.substanceKeyTypeForInvitroPharmacologyConfig = this.generalService.getSubstanceKeyTypeForInvitroPharmacologyConfig(); @@ -154,10 +162,12 @@ export class InvitroPharmacologyAssayDataImportComponent implements OnInit { const bstr: string = e.target.result; const wb: XLSX.WorkBook = XLSX.read(bstr, { type: 'binary' }); - // Read the first Excel Spreadsheet - const wsname = wb.SheetNames[0]; + // Read the Excel Spreadsheet that has Assay data + const wsname = wb.SheetNames[1]; const ws: XLSX.WorkSheet = wb.Sheets[wsname]; + // Read the 'Controlled Vocabularies' sheet + // If header is specified, the first row is considered a data row; if header is not specified, // the first row is the header row and not considered data. // Null values are returned when raw is true but are skipped when false. @@ -166,63 +176,72 @@ export class InvitroPharmacologyAssayDataImportComponent implements OnInit { // 23 Fields this.importedAssayJson.forEach((element, index) => { if (element) { - element["externalAssaySource"] = this.replaceUndefinedValue(element["External Assay Source"]); - element["externalAssayId"] = this.replaceUndefinedValue(element["External Assay ID"]); + + // Create Assay Set Object + this.createAssaySet(element); + + element["assayId"] = this.replaceUndefinedValue(element["Assay ID"]); + element["externalAssaySource"] = this.replaceUndefinedValue(element["External Assay Source *"]); + element["externalAssayId"] = this.replaceUndefinedValue(element["External Assay ID *"]); element["externalAssayReferenceUrl"] = this.replaceUndefinedValue(element["External Assay Reference URL"]); element["assayTitle"] = this.replaceUndefinedValue(element["Assay Title"]); + element["assayFormat"] = this.replaceUndefinedValue(element["Assay Format"]); element["assayMode"] = this.replaceUndefinedValue(element["Assay Mode"]); element["bioassayType"] = this.replaceUndefinedValue(element["Bioassay Type"]); element["bioassayClass"] = this.replaceUndefinedValue(element["Bioassay Class"]); element["studyType"] = this.replaceUndefinedValue(element["Study Type"]); element["detectionMethod"] = this.replaceUndefinedValue(element["Detection Method"]); - - element["presentationType"] = this.replaceUndefinedValue(element["Presention Type"]); + element["presentationType"] = this.replaceUndefinedValue(element["Presentation Type"]); element["presentation"] = this.replaceUndefinedValue(element["Presentation"]); element["publicDomain"] = this.replaceUndefinedValue(element["Public Domain"]); - element["targetName"] = this.replaceUndefinedValue(element["Assay Target Name"]); - element["targetNameApprovalId"] = this.replaceUndefinedValue(element["Assay Target Name Approval ID"]); + element["targetSpecies"] = this.replaceUndefinedValue(element["Target Species"]); + element["targetName"] = this.replaceUndefinedValue(element["Assay Target Name *"]); + element["targetNameApprovalId"] = this.replaceUndefinedValue(element["Assay Target Name Approval ID"]); + element["humanHomologTarget"] = this.replaceUndefinedValue(element["Human Homolog Target Name"]); element["humanHomologTargetApprovalId"] = this.replaceUndefinedValue(element["Human homolog Target Name Approval ID"]); + element["ligandSubstrate"] = this.replaceUndefinedValue(element["Ligand/Substate"]); element["ligandSubstrateApprovalId"] = this.replaceUndefinedValue(element["Ligand/Substrate Approval ID"]); element["standardLigandSubstrateConcentration"] = this.replaceUndefinedValue(element["Standard Ligand/Substrate Concentration"]); element["standardLigandSubstrateConcentrationUnits"] = this.replaceUndefinedValue(element["Standard Ligand/Substrate Concentration Units"]); - // Assay Set - let assaySet = this.replaceUndefinedValue(element["Assay Set"]); - this.createAssaySet(element); + // Create Analytes Object + this.createAnalytes(element); - // Delete the key. 23 Fields - delete element["External Assay Source"]; - delete element["External Assay ID"]; + // Delete 25 keys from Excel File. Only want to keep the JSON format Key and remove + // key with Column formats that have spaces and capitalized + delete element["Assay Set *"] + delete element["Assay ID"]; + delete element["External Assay Source *"]; + delete element["External Assay ID *"]; delete element["External Assay Reference URL"]; delete element["Assay Title"]; delete element["Assay Format"]; delete element["Assay Mode"]; delete element["Bioassay Type"]; delete element["Bioassay Class"]; - delete element["Study Type"]; - delete element["Detection Method"]; + delete element["Study Type"]; delete element["Detection Method"]; - delete element["Presention Type"]; + delete element["Presentation Type"]; delete element["Presentation"]; delete element["Public Domain"]; - delete element["Assay Target Name"]; + delete element["Assay Target Name *"]; delete element["Assay Target Name Approval ID"]; delete element["Target Species"]; delete element["Human Homolog Target Name"]; delete element["Human homolog Target Name Approval ID"] - delete element["Ligand/Substate"] + delete element["Ligand/Substate"] delete element["Ligand/Substrate Approval ID"] delete element["Standard Ligand/Substrate Concentration"] delete element["Standard Ligand/Substrate Concentration Units"] - // delete element["Assay Set"] + delete element["Analytes"] // Add to list this.importDataList.push(element); @@ -230,9 +249,12 @@ export class InvitroPharmacologyAssayDataImportComponent implements OnInit { }); // LOOP: importedAssayJson - this.disableValidateButton = 'false'; - // this.disableImportButton = 'false'; - + // Only enable validate button if there are records in the Excel file + if (this.importedAssayJson.length > 0) { + this.disableValidateButton = 'false'; + } else { + this.submitMessage = "Excel file does not contain any data. Please add data and try again."; + } } // reader.onload reader.readAsBinaryString(target.files[0]); @@ -248,44 +270,109 @@ export class InvitroPharmacologyAssayDataImportComponent implements OnInit { createAssaySet(element: any) { const newAssaySet: InvitroAssaySet = {}; - let sets: Array = []; - let assaySet = this.replaceUndefinedValue(element["Assay Set"]); - newAssaySet.assaySet = assaySet; - sets.push(newAssaySet); - element["invitroAssaySets"] = sets; + let assaySets: Array = []; + let assaySetFromFile = this.replaceUndefinedValue(element["Assay Set *"]); + + // If multiple Assay Set are separated by pipe | delimter in the Excel file, separate + // each Assay Set as a separate object + if (assaySetFromFile) { + if (assaySetFromFile.includes("|")) { + let assaySetArray = assaySetFromFile.split("|"); + assaySetArray.forEach(assaySt => { + if (assaySt) { + const newAssaySet: InvitroAssaySet = {}; + + newAssaySet.assaySet = assaySt; + assaySets.push(newAssaySet); + } + }); + } else { + newAssaySet.assaySet = assaySetFromFile; + assaySets.push(newAssaySet); + } + + element["invitroAssaySets"] = assaySets; + } + } + + createAnalytes(element: any) { + const newAssayAnalyte: InvitroAssayAnalyte = {}; + + let analytes: Array = []; + let analytesFromFile = this.replaceUndefinedValue(element["Analytes"]); + + // If multiple Anaytes are separated by pipe | delimter in the Excel file, separate + // each Analyte as a separate object + if (analytesFromFile) { + if (analytesFromFile.includes("|")) { + let analyteArray = analytesFromFile.split("|"); + analyteArray.forEach(analyte => { + if (analyte) { + const newAssayAnalyte: InvitroAssayAnalyte = {}; + + newAssayAnalyte.analyte = analyte; + analytes.push(newAssayAnalyte); + } + }); + } else { + newAssayAnalyte.analyte = analytesFromFile; + analytes.push(newAssayAnalyte); + } + + element["invitroAssayAnalytes"] = analytes; + } } validate(): void { - this.isLoading = true; this.serverError = false; + this.isLoading = true; this.loadingService.setLoading(true); - this.submitMessage = "Validating Assay records, please wait ....."; - this.importValidateMessageArray = []; + // Loop through each Assay JSON Record, and save into the database this.importedAssayJson.forEach((element, index) => { - // NEED THIS, otherwise the index value is getting changed before validation comes back - const localIndex = index; - // setTimeout(() => { if (element) { + let validationMessages: Array = []; const assay = JSON.parse(JSON.stringify(element)); this.invitroPharmacologyService.assay = assay; + this.submitMessage = 'Validating Assay records in Excel file ' + (index + 1) + ' of ' + this.importedAssayJson.length + ', please wait .....'; + + // Validate Assay - this.invitroPharmacologyService.validateAssay().pipe(take(1)).subscribe(results => { - this.submissionMessage = null; + const validateSubscription = this.invitroPharmacologyService.validateAssay().subscribe(response => { + + // Populated Substance Key and Substance Key Type for Target Name, Homolog, Substrate + if (element['targetName']) { + this.getSubstanceNameDetails(element, element['targetName'], this.TARGET_NAME, validationMessages, index); + } + if (element['humanHomologTarget']) { + this.getSubstanceNameDetails(element, element['humanHomologTarget'], this.HUMAN_HOMOLOG_TARGET, validationMessages, index) + } + if (element['ligandSubstrate']) { + this.getSubstanceNameDetails(element, element['ligandSubstrate'], this.LIGAND_SUBSTRATE, validationMessages, index) + } // NEED this two fields to check if valid or not - this.validationMessages = results.validationMessages.filter( + let validationMessagesResponse = response.validationMessages.filter( message => message.messageType.toUpperCase() === 'ERROR' || message.messageType.toUpperCase() === 'WARNING'); - this.validationResult = results.valid; - const saved = { 'indexRecord': index, 'externalAssaySource': assay.externalAssaySource, 'externalAssayId': assay.externalAssayId, validationMessages: this.validationMessages, 'valid': results.valid } + if (validationMessagesResponse && validationMessagesResponse.length > 0) { + validationMessagesResponse.forEach(validation => { + if (validation) { + validationMessages.push(validation); + } + }); + } + + const saved = { 'indexRecord': index, 'invitroAssaySets': assay.invitroAssaySets, 'externalAssaySource': assay.externalAssaySource, 'externalAssayId': assay.externalAssayId, 'targetName': assay.targetName, 'validationMessages': validationMessages, 'valid': response.valid } + this.importValidateMessageArray.push(saved); + // All rows in the Excel file has been validated if (this.importDataList.length == this.importValidateMessageArray.length) { // Get the index if the value exists in the array @@ -300,15 +387,15 @@ export class InvitroPharmacologyAssayDataImportComponent implements OnInit { this.isAllRecordValid = true; } + if (this.isAllRecordValid == true) { + this.disableImportButton = 'false'; + } + // SORT the validation array by id this.importValidateMessageArray.sort((a, b) => { return a.indexRecord - b.indexRecord; }); - if (this.isAllRecordValid == true) { - this.disableImportButton = 'false'; - } - this.isAllRecordValidated = true; this.submitMessage = ''; @@ -321,11 +408,25 @@ export class InvitroPharmacologyAssayDataImportComponent implements OnInit { this.loadingService.setLoading(false); this.isLoading = false; }); + this.subscriptions.push(validateSubscription); } }); // for import data list } + setValidationMessage(message: string, validationMessages: Array, index: number) { + const validate: ValidationMessage = {}; + validate.message = message; + validate.messageType = 'ERROR'; + validationMessages.push(validate); + + this.importValidateMessageArray[index].valid = false; + + // Disable Import to Database button + this.disableImportButton = "true"; + + } + addServerError(error: any): void { this.serverError = true; this.validationResult = false; @@ -348,6 +449,11 @@ export class InvitroPharmacologyAssayDataImportComponent implements OnInit { } this.validationMessages = [message]; this.showSubmissionMessages = true; + + // Display error message + if (this.validationMessages.length > 0) { + this.submitMessage = "There is an error. " + message.message; + } } importAssayJSONIntoDatabase() { @@ -359,8 +465,6 @@ export class InvitroPharmacologyAssayDataImportComponent implements OnInit { this.isLoading = true; this.loadingService.setLoading(this.isLoading); - this.submitMessage = "Saving Assay records into the database, please wait ....."; - // Loop through each Assay JSON Record, and save into the database this.importedAssayJson.forEach((element, index) => { @@ -368,16 +472,16 @@ export class InvitroPharmacologyAssayDataImportComponent implements OnInit { if (element) { this.invitroPharmacologyService.assay = JSON.parse(JSON.stringify(element)); - // this.populateSubstanceKey(this.invitroPharmacologyService.assay.targetName, this.TARGET_NAME); - // this.populateSubstanceKey(this.invitroPharmacologyService.assay.targetName, this.TARGET_NAME); - // this.populateSubstanceKey(this.invitroPharmacologyService.assay.targetName, this.TARGET_NAME); + this.submitMessage = 'Saving Assay records into the database ' + (index + 1) + ' of ' + this.importedAssayJson.length + ', please wait .....'; // Save Into the database - this.invitroPharmacologyService.saveAssay().subscribe(response => { + const saveSubscription = this.invitroPharmacologyService.saveAssay().subscribe(response => { if (response) { if (response.id) { - const saved = { 'indexRecord': index, 'externalAssaySource': response.externalAssaySource, 'externalAssayId': response.externalAssayId, 'saved': 'Yes', 'savedId': response.id } + this.totalRecordSavedInDatabase = index + 1; + + const saved = { 'indexRecord': index, 'assayId': response.assayId, 'externalAssaySource': response.externalAssaySource, 'externalAssayId': response.externalAssayId, 'saved': 'Yes', 'savedId': response.id } this.importSaveMessageArray.push(saved); } @@ -406,64 +510,109 @@ export class InvitroPharmacologyAssayDataImportComponent implements OnInit { } } }, error => { + // Error occured during saving + console.log("ERROR DURING SAVING ASSAY IMPORT " + error) + const saved = { 'indexRecord': index, 'assayId': element['assayId'], 'externalAssaySource': element['externalAssaySource'], 'externalAssayId': element['externalAssayId'], 'saved': 'No', 'savedId': '', 'error': error } + this.importSaveMessageArray.push(saved); this.submitMessage = ""; - this.isLoading = false; this.loadingService.setLoading(this.isLoading); - }); + }); // save + + this.subscriptions.push(saveSubscription); } }, 10000); // timeout }); + } - populateSubstanceKey(ingredientName: string, fieldName: string) { + getSubstanceNameDetails(element: any, ingredientName: string, fieldName: string, validationMessages: Array, index: number) { - /****************************************************************/ - /* SUBSTANCE KEY RESOLVER BEGIN */ - /****************************************************************/ - // Get Substance record by Ingredient/Substance Name, to get Substance UUID and Approval ID - const substanceSubscribe = this.generalService.getSubstanceByName(ingredientName).subscribe(response => { + let found = false; + + const substanceSubscribe = this.generalService.getSubstanceByNameExactMatch(ingredientName).subscribe(response => { if (response) { if (response.content && response.content.length > 0) { // Loop through the search results and if the Substance/Ingredient name is same as name in the search // result, select that substance - response.content.forEach(substance => { - + let substances = response.content; + for (let i = 0; i < substances.length; i++) { + let substance = substances[i]; if (substance) { - if (substance._name) { - - // If Substance Name is same as in the Search Result - if (substance._name === ingredientName) { - - /****************************************************************/ - /* SUBSTANCE KEY RESOLVER BEGIN */ - /****************************************************************/ - let substanceKey = this.generalService.getSubstanceKeyBySubstanceResolver(substance, this.substanceKeyTypeForInvitroPharmacologyConfig); - - // Set the Substance Key and Substance Key Type - if (fieldName && fieldName === this.TARGET_NAME) { - // this.assay.targetNameApprovalId = substance.approvalID; - // this.assay.targetNameSubstanceKey = substanceKey; - //this.assay.targetNameSubstanceKeyType = this.substanceKeyTypeForInvitroPharmacologyConfig; + if (substance.names && substance.names.length > 0) { + + substance.names.forEach(nameObj => { + if (nameObj && nameObj.name === ingredientName) { + + found = true; + + let substanceKey = this.generalService.getSubstanceKeyBySubstanceResolver(substance, this.substanceKeyTypeForInvitroPharmacologyConfig); + + if (fieldName == this.TARGET_NAME) { + element["targetNameSubstanceUuid"] = substance.uuid; + element["targetNameSubstanceKey"] = substanceKey; + element["targetNameSubstanceKeyType"] = this.substanceKeyTypeForInvitroPharmacologyConfig; + + if ((element["targetNameApprovalId"]) && (element["targetNameApprovalId"] !== substance.approvalID)) { + this.setValidationMessage(this.TARGET_NAME + ' Approval ID "' + element["targetNameApprovalId"] + '" in Excel file does not match with Approval ID "' + substance.approvalID + '" in the database. Please fix in the Excel file and then import again', validationMessages, index); + } + } + else if (fieldName == this.HUMAN_HOMOLOG_TARGET) { + element["humanHomologTargetSubstanceKey"] = substanceKey; + element["humanHomologTargetSubstanceKeyType"] = this.substanceKeyTypeForInvitroPharmacologyConfig; + + if ((element["humanHomologTargetApprovalId"]) && (element["humanHomologTargetApprovalId"] !== substance.approvalID)) { + this.setValidationMessage(this.HUMAN_HOMOLOG_TARGET + ' Approval ID "' + element["humanHomologTargetApprovalId"] + '" in Excel file does not match with Approval ID "' + substance.approvalID + '" in the database. Please fix in the Excel file and then import again', validationMessages, index); + } + } + else if (fieldName == this.LIGAND_SUBSTRATE) { + element["ligandSubstrateSubstanceKey"] = substanceKey; + element["ligandSubstrateSubstanceKeyType"] = this.substanceKeyTypeForInvitroPharmacologyConfig; + + if ((element["ligandSubstrateApprovalId"]) && (element["ligandSubstrateApprovalId"] !== substance.approvalID)) { + this.setValidationMessage(this.LIGAND_SUBSTRATE + ' Approval ID "' + element["ligandSubstrateApprovalId"] + '" in Excel file does not match with Approval ID "' + substance.approvalID + '" in the database. Please fix in the Excel file and then import again', validationMessages, index); + } + } } - /* SUBSTANCE KEY RESOLVER END */ + }); // substance names for loop + } // if names exist + } // if substance exists + } // substances for loop - } // if substance._name === ingredientName + if (found == false) { + this.setValidationMessage(fieldName + ' "' + ingredientName + '" does not exist in the database. Please register this substance first and then import again', validationMessages, index); + } - } // if substance._name is not null - } // if Substance exists + if (fieldName == this.TARGET_NAME) { + this.targetNameCheckCompleted = true; + } else if (fieldName == this.HUMAN_HOMOLOG_TARGET) { + this.humanHomologCheckCompleted = true; + } else if (fieldName == this.LIGAND_SUBSTRATE) { + this.ligandCheckCompleted = true; + } - }); // LOOP Substance search result + // Enable Database Import button + if ( this.targetNameCheckCompleted && this.targetNameCheckCompleted && this.targetNameCheckCompleted) { + // this.disableImportButton = "false"; + } + + } // if content > 0 + else { + this.setValidationMessage(fieldName + ' "' + ingredientName + '" does not exist in the database. Please register this substance first and then import again', validationMessages, index); + } + } // if response + else { + this.setValidationMessage(fieldName + ' "' + ingredientName + '" does not exist in the database. Please register this substance first and then import again', validationMessages, index); + } + }, error => { + this.setValidationMessage(fieldName + ' "' + ingredientName + '" does not exist in the database. Please register this substance first and then import again', validationMessages, index); + }, () => { + - } // if response content > 0 - } // if response }); this.subscriptions.push(substanceSubscribe); - - /* SUBSTANCE KEY RESOLVER END */ - } showJSON(): void { @@ -475,7 +624,7 @@ export class InvitroPharmacologyAssayDataImportComponent implements OnInit { json = this.importedAssayJson; } - let data = {jsonData: json, jsonFilename: jsonFilename}; + let data = { jsonData: json, jsonFilename: jsonFilename }; const dialogRef = this.dialog.open(JsonDialogFdaComponent, { width: '90%', @@ -489,4 +638,20 @@ export class InvitroPharmacologyAssayDataImportComponent implements OnInit { this.subscriptions.push(dialogSubscription); } + openModalSave() { + const dialogRef = this.dialog.open(this.saveTemplate, { + width: '60%', + height: '30%' + }); + + this.overlayContainer.style.zIndex = '1002'; + + dialogRef.afterClosed().subscribe(result => { + this.overlayContainer.style.zIndex = null; + }); + } + + close() { + this.dialog.closeAll(); + } } diff --git a/src/app/fda/invitro-pharmacology/invitro-pharmacology-details/invitro-pharmacology-details.component.html b/src/app/fda/invitro-pharmacology/invitro-pharmacology-details/invitro-pharmacology-details.component.html index 201423b1f..e2acb53d4 100644 --- a/src/app/fda/invitro-pharmacology/invitro-pharmacology-details/invitro-pharmacology-details.component.html +++ b/src/app/fda/invitro-pharmacology/invitro-pharmacology-details/invitro-pharmacology-details.component.html @@ -158,7 +158,7 @@ Target Name: -
    + - + {{assay.targetName}} diff --git a/src/app/fda/invitro-pharmacology/invitro-pharmacology-form/invitro-pharmacology-assay-form/invitro-pharmacology-assay-form.component.ts b/src/app/fda/invitro-pharmacology/invitro-pharmacology-form/invitro-pharmacology-assay-form/invitro-pharmacology-assay-form.component.ts index 181c450cd..69d2f92ce 100644 --- a/src/app/fda/invitro-pharmacology/invitro-pharmacology-form/invitro-pharmacology-assay-form/invitro-pharmacology-assay-form.component.ts +++ b/src/app/fda/invitro-pharmacology/invitro-pharmacology-form/invitro-pharmacology-assay-form/invitro-pharmacology-assay-form.component.ts @@ -145,7 +145,6 @@ export class InvitroPharmacologyAssayFormComponent implements OnInit, OnDestroy this.invitroPharmacologyService.loadAssayOnly(response); this.assay = this.invitroPharmacologyService.assay; - console.log("AAAAAAAAAAAAAAAA " + JSON.stringify(this.assay)); // Get All the Assay Sets for checkboxes on the form this.getAllAssaySets(); diff --git a/src/app/fda/invitro-pharmacology/invitro-pharmacology-screening-data-import/invitro-pharmacology-screening-data-import.component.html b/src/app/fda/invitro-pharmacology/invitro-pharmacology-screening-data-import/invitro-pharmacology-screening-data-import.component.html index 4e4def7d0..14b7c33cb 100644 --- a/src/app/fda/invitro-pharmacology/invitro-pharmacology-screening-data-import/invitro-pharmacology-screening-data-import.component.html +++ b/src/app/fda/invitro-pharmacology/invitro-pharmacology-screening-data-import/invitro-pharmacology-screening-data-import.component.html @@ -8,11 +8,20 @@ -
    - -
    +
    + +
    + +
    +
    + + Required Field + +
    Reference Source/Citation *: - {{invitroReference.sourceCitation}}
    + + {{invitroReference.sourceCitation}} + + + + Required Field + +
    Reference Source Id: {{invitroReference.sourceId}}
    @@ -157,7 +182,14 @@

    Laboratory Name *: - {{invitroLaboratory.laboratoryName}}
    + {{invitroLaboratory.laboratoryName}} + + + + Required Field + +
    Laboratory Affiliation: {{invitroLaboratory.laboratoryAffiliation}}
    @@ -196,7 +228,14 @@ 3. Sponsor Information

    Sponsor Contact Name *: - {{invitroSponsor.sponsorContactName}}
    + {{invitroSponsor.sponsorContactName}} + + + + Required Field + +
    Sponsor Affiliation: {{invitroSponsor.sponsorAffiliation}}
    @@ -226,10 +265,17 @@
    Sponsor Report Submitter Name *: - {{submitter.sponsorReportSubmitterName}}
    + {{submitter.sponsorReportSubmitterName}} + + + + Required Field + +
    Sponsor Report Submitter Title: - {{submitter.sponsorRepoortSubmitterTitle}}
    + {{submitter.sponsorReportSubmitterTitle}}
    Sponsor Report Submitter Affiliation: {{submitter.sponsorReportSubmitterAffiliation}}
    @@ -252,10 +298,22 @@ 5. Report Information

    Report Number *: - {{invitroSponsorReport.reportNumber}}
    - - Report Date *: - {{invitroSponsorReport.reportDate}}
    + {{invitroSponsorReport.reportNumber}} + + + + Required Field + +
    + + Report Date: + + + {{invitroSponsorReport.reportDate}} + +
    @@ -272,11 +330,18 @@ 6. Test Agent Information

    - Test Agent ID (Company Code) *: + Test Agent ID (Company Code): {{invitroTestAgent.testAgentCompanyCode}}
    Test Agent Name (FDA) *: - {{invitroTestAgent.testAgent}}
    + {{invitroTestAgent.testAgent}} + + + + Required Field + +
    Test Agent Approval ID/UNII: {{invitroTestAgent.testAgentApprovalId}}
    @@ -296,8 +361,14 @@ 7. Batch Information

    - Batch Number: - {{invitroResultInfo.batchNumber}}
    + Batch Number *: + {{invitroResultInfo.batchNumber}} + + + Required Field + +
    @@ -318,26 +389,64 @@ + - - - + + + - - - - + + + + + + + + + - - - + + +
    NumberAssay Found In Database External Assay Source * External Assay ID * Assay ID Control Substance Type of ControlControl Value TypeControl ValueControl Value UnitsControl Result TypeControl Reference ValueControl Reference Value Units
    {{(index+1)}}{{ctrl.externalAssaySource}}{{ctrl.externalAssayId}}
    {{(indexControl+1)}} + +
    YES
    +
    + +
    NO + +
    +
    + + + Register Assay + +
    + +
    ERROR
    +
    +
    {{ctrl.externalAssaySource}} + + + + Required Field + + {{ctrl.externalAssayId}} + + + + Required Field + + {{ctrl.assayId}} {{ctrl.control}} {{ctrl.controlType}}{{ctrl.controlValueType}}{{ctrl.controlValue}}{{ctrl.controlValueUnits}}{{ctrl.controlResultType}}{{ctrl.controlReferenceValue}}{{ctrl.controlReferenceValueUnits}}
    @@ -356,14 +465,18 @@ list_alt Sheet 5.Assay Results


    + {{resultMessage}} +

    +
    + - + + - @@ -382,9 +495,9 @@ - + - + + + diff --git a/src/app/fda/invitro-pharmacology/invitro-pharmacology-screening-data-import/invitro-pharmacology-screening-data-import.component.scss b/src/app/fda/invitro-pharmacology/invitro-pharmacology-screening-data-import/invitro-pharmacology-screening-data-import.component.scss index 7c7fcae5e..00b92c70a 100644 --- a/src/app/fda/invitro-pharmacology/invitro-pharmacology-screening-data-import/invitro-pharmacology-screening-data-import.component.scss +++ b/src/app/fda/invitro-pharmacology/invitro-pharmacology-screening-data-import/invitro-pharmacology-screening-data-import.component.scss @@ -354,6 +354,10 @@ padding-top: 17px; } +.padright30px { + padding-right: 30px; +} + .bordergray { border: 1px solid var(--regular-grey-color); } @@ -450,6 +454,10 @@ font-size: 20px; } +.fontbold { + font-weight: 600; +} + .backgroundgreen { background-color: var(--regular-green-color); } @@ -596,6 +604,7 @@ table.tableStyle { max-width: 100%; text-align: left; border-collapse: collapse; + padding-right: 60px; } table.tableStyle td, table.tableStyle th { diff --git a/src/app/fda/invitro-pharmacology/invitro-pharmacology-screening-data-import/invitro-pharmacology-screening-data-import.component.ts b/src/app/fda/invitro-pharmacology/invitro-pharmacology-screening-data-import/invitro-pharmacology-screening-data-import.component.ts index 074737364..770d97748 100644 --- a/src/app/fda/invitro-pharmacology/invitro-pharmacology-screening-data-import/invitro-pharmacology-screening-data-import.component.ts +++ b/src/app/fda/invitro-pharmacology/invitro-pharmacology-screening-data-import/invitro-pharmacology-screening-data-import.component.ts @@ -42,8 +42,12 @@ import { export class InvitroPharmacologyScreeningDataImportComponent implements OnInit { + private TEST_AGENT = "Test Agent"; + private subscriptions: Array = []; + invtroReferences: Array = []; + invitroReference: InvitroReference = {}; invitroLaboratory: InvitroLaboratory = {}; invitroSponsor: InvitroSponsor = {}; @@ -60,12 +64,14 @@ export class InvitroPharmacologyScreeningDataImportComponent implements OnInit { invitroAssayResult: InvitroAssayResult = {}; invitroResultInfo: InvitroAssayResultInformation = {} + requiredFieldMissingArray: Array = []; importDataList: Array = []; importedBulkAssayJson: Array = [{}]; importedAssayJson: any; message = ''; submitMessage = ''; + resultMessage = ''; disableImportButton = "true"; isExcelDataLoaded = false; isAdmin = false; @@ -109,6 +115,7 @@ export class InvitroPharmacologyScreeningDataImportComponent implements OnInit { else { // Empty the list this.importDataList.length = 0; + this.requiredFieldMissingArray = [{}]; // Assign FileReader const reader: FileReader = new FileReader(); @@ -124,26 +131,26 @@ export class InvitroPharmacologyScreeningDataImportComponent implements OnInit { cellNF: true }); - this.readInvitroReference(workbook); + this.getInvitroReference(workbook); - this.createInvitroLaboratory(workbook); + this.getInvitroLaboratory(workbook); - this.createInvitroSponsor(workbook); + this.getInvitroSponsor(workbook); - this.createInvitroSponsorReport(workbook); + this.getInvitroSponsorReport(workbook); - this.createInvitroSponsorSubmitters(workbook); + this.getInvitroSponsorSubmitters(workbook); - this.createInvitroTestAgent(workbook); + this.getInvitroTestAgent(workbook); - this.createInvitroBatchNumber(workbook); + this.getInvitroBatchNumber(workbook); - this.createInvitroControls(workbook); + this.getInvitroControls(workbook); - this.createInvitroResults(workbook); + this.getInvitroResults(workbook); - // Validate - this.validateExcelImportData(); + // Validate all data from Excel file + this.validate(); } // reader.onload @@ -153,7 +160,7 @@ export class InvitroPharmacologyScreeningDataImportComponent implements OnInit { } - readInvitroReference(workbook: XLSX.WorkBook) { + getInvitroReference(workbook: XLSX.WorkBook) { // Read the Second Excel Spreadsheet, the worksheet index starts with 0. // Read Sheet "1. Reference and Laboratory" const worksheetName = workbook.SheetNames[1]; @@ -186,13 +193,27 @@ export class InvitroPharmacologyScreeningDataImportComponent implements OnInit { if (worksheetRefLab[cellKey].v) { if (worksheetRefLab[cellKey].v.trim() === 'Reference Source Type *') { this.invitroReference.sourceType = this.getValue(worksheetRefLab[cellKeyValue]); - } else if (worksheetRefLab[cellKey].v.trim() === 'Reference Source/Citation *') { + + // ** Validate Required field + if (!this.invitroReference.sourceType) { + this.requiredFieldMissingArray[0].sourceType = true; + } + } + else if (worksheetRefLab[cellKey].v.trim() === 'Reference Source/Citation *') { this.invitroReference.sourceCitation = this.getValue(worksheetRefLab[cellKeyValue]); - } else if (worksheetRefLab[cellKey].v.trim() === 'Reference Source Id') { + + // ** Validate Required field + if (!this.invitroReference.sourceCitation) { + this.requiredFieldMissingArray[0].sourceCitation = true; + } + } + else if (worksheetRefLab[cellKey].v.trim() === 'Reference Source Id') { this.invitroReference.sourceId = this.getValue(worksheetRefLab[cellKeyValue]); - } else if (worksheetRefLab[cellKey].v.trim() === 'Reference Digital Object Identifier') { + } + else if (worksheetRefLab[cellKey].v.trim() === 'Reference Digital Object Identifier') { this.invitroReference.digitalObjectIdentifier = this.getValue(worksheetRefLab[cellKeyValue]); } + } // if value is not null } // else @@ -200,13 +221,18 @@ export class InvitroPharmacologyScreeningDataImportComponent implements OnInit { } // for loop Column } // for loop Row + if (this.invitroReference) { + this.invitroReference.primaryReference = true; + this.invtroReferences.push(this.invitroReference); + } + // Set Reference to InvitroAssayResultInformation // this.invitroResultInfo.invitroReferences[0].primaryReference = true; // this.invitroResultInfo.invitroReferences[0] = this.invitroReference; } - createInvitroLaboratory(workbook: XLSX.WorkBook) { + getInvitroLaboratory(workbook: XLSX.WorkBook) { // Read the Second Excel Spreadsheet, the worksheet index starts with 0. // Read Sheet "1. Reference and Laboratory" const worksheetName = workbook.SheetNames[1]; @@ -236,6 +262,11 @@ export class InvitroPharmacologyScreeningDataImportComponent implements OnInit { if (worksheetRefLab[cellKey].v) { if (worksheetRefLab[cellKey].v.trim() === 'Laboratory Name *') { this.invitroLaboratory.laboratoryName = this.getValue(worksheetRefLab[cellKeyValue]); + + // ** Validate Required field + if (!this.invitroLaboratory.laboratoryName) { + this.requiredFieldMissingArray[0].laboratoryName = true; + } } else if (worksheetRefLab[cellKey].v.trim() === 'Laboratory Affiliation') { this.invitroLaboratory.laboratoryAffiliation = this.getValue(worksheetRefLab[cellKeyValue]); } else if (worksheetRefLab[cellKey].v.trim() === 'Laboratory Type') { @@ -259,7 +290,7 @@ export class InvitroPharmacologyScreeningDataImportComponent implements OnInit { } - createInvitroSponsor(workbook: XLSX.WorkBook) { + getInvitroSponsor(workbook: XLSX.WorkBook) { // Read the Third Excel Spreadsheet, the worksheet index starts with 0. // Read Sheet "2. Sponsor,Submitter,Report" const worksheetName = workbook.SheetNames[2]; @@ -289,6 +320,11 @@ export class InvitroPharmacologyScreeningDataImportComponent implements OnInit { if (worksheetRefLab[cellKey].v) { if (worksheetRefLab[cellKey].v.trim() === 'Sponsor Contact Name *') { this.invitroSponsor.sponsorContactName = this.getValue(worksheetRefLab[cellKeyValue]); + + // ** Validate Required field + if (!this.invitroSponsor.sponsorContactName) { + this.requiredFieldMissingArray[0].sponsorContactName = true; + } } else if (worksheetRefLab[cellKey].v.trim() === 'Sponsor Affiliation') { this.invitroSponsor.sponsorAffiliation = this.getValue(worksheetRefLab[cellKeyValue]); } else if (worksheetRefLab[cellKey].v.trim() === 'Sponsor Street Address') { @@ -310,7 +346,7 @@ export class InvitroPharmacologyScreeningDataImportComponent implements OnInit { } - createInvitroSponsorSubmitters(workbook: XLSX.WorkBook) { + getInvitroSponsorSubmitters(workbook: XLSX.WorkBook) { // Read the Third Excel Spreadsheet, the worksheet index starts with 0. // Read Sheet "2.Sponsor,Submitter,Report" const worksheetName = workbook.SheetNames[2]; @@ -344,15 +380,20 @@ export class InvitroPharmacologyScreeningDataImportComponent implements OnInit { if (worksheet[cellKey].v.trim() === 'Sponsor Report Submitter Name *') { this.invitroSponsorSubmitter.sponsorReportSubmitterName = this.getValue(worksheet[cellKeyValue]); + + // ** Validate Required field + if (!this.invitroSponsorSubmitter.sponsorReportSubmitterName) { + this.requiredFieldMissingArray[0].sponsorReportSubmitterName = true; + } } else if (worksheet[cellKey].v.trim() === 'Sponsor Report Submitter Title') { - this.invitroSponsorSubmitter.sponsorRepoortSubmitterTitle = this.getValue(worksheet[cellKeyValue]); + this.invitroSponsorSubmitter.sponsorReportSubmitterTitle = this.getValue(worksheet[cellKeyValue]); } else if (worksheet[cellKey].v.trim() === 'Sponsor Report Submitter Affiliation') { this.invitroSponsorSubmitter.sponsorReportSubmitterAffiliation = this.getValue(worksheet[cellKeyValue]); } else if (worksheet[cellKey].v.trim() === 'Sponsor Report Submitter Email') { this.invitroSponsorSubmitter.sponsorReportSubmitterEmail = this.getValue(worksheet[cellKeyValue]); } else if (worksheet[cellKey].v.trim() === 'Sponsor Report Submitter Phone Number') { this.invitroSponsorSubmitter.sponsorReportSubmitterPhoneNumber = this.getValue(worksheet[cellKeyValue]); - } else if (worksheet[cellKey].v.trim() === 'Sponsor Report Submitter Assay Type') { + } else if (worksheet[cellKey].v.trim() === 'Sponsor Report Submitter Bioassay Type') { this.invitroSponsorSubmitter.sponsorReportSubmitterAssayType = this.getValue(worksheet[cellKeyValue]); } } // if value is not null @@ -365,7 +406,7 @@ export class InvitroPharmacologyScreeningDataImportComponent implements OnInit { //this.invitroSponsorReport.invitroSponsorSubmitters.push(this.invitroSponsorSubmitter); } - createInvitroSponsorReport(workbook: XLSX.WorkBook) { + getInvitroSponsorReport(workbook: XLSX.WorkBook) { // Read the Third Excel Spreadsheet, the worksheet index starts with 0. // Read Sheet "2. Sponsor,Submitter,Report" const worksheetName = workbook.SheetNames[2]; @@ -395,8 +436,12 @@ export class InvitroPharmacologyScreeningDataImportComponent implements OnInit { if (worksheet[cellKey].v) { if (worksheet[cellKey].v.trim() === 'Report Number *') { this.invitroSponsorReport.reportNumber = this.getValue(worksheet[cellKeyValue]); - } else if (worksheet[cellKey].v.trim() === 'Report Date *') { + // ** Validate Required field + if (!this.invitroSponsorReport.reportNumber) { + this.requiredFieldMissingArray[0].reportNumber = true; + } + } else if (worksheet[cellKey].v.trim() === 'Report Date') { // Convert Report Date from Number to Date datatype if (this.getValue(worksheet[cellKeyValue])) { const parsedReportDate: Date = new Date(this.getValue(worksheet[cellKeyValue])); @@ -413,7 +458,7 @@ export class InvitroPharmacologyScreeningDataImportComponent implements OnInit { } - createInvitroTestAgent(workbook: XLSX.WorkBook) { + getInvitroTestAgent(workbook: XLSX.WorkBook) { // Read the Fourth Excel Spreadsheet, the worksheet index starts with 0. // Read Sheet "3. Test Agent,Batch Number" const worksheetName = workbook.SheetNames[3]; @@ -441,10 +486,15 @@ export class InvitroPharmacologyScreeningDataImportComponent implements OnInit { var cellKeyValue = XLSX.utils.encode_cell({ r: R, c: C + 1 }); if (worksheet[cellKey].v) { - if (worksheet[cellKey].v.trim() === 'Test Agent ID (Company Code) *') { + if (worksheet[cellKey].v.trim() === 'Test Agent ID (Company Code)') { this.invitroTestAgent.testAgentCompanyCode = this.getValue(worksheet[cellKeyValue]); } else if (worksheet[cellKey].v.trim() === 'Test Agent Name (FDA) *') { this.invitroTestAgent.testAgent = this.getValue(worksheet[cellKeyValue]); + + // ** Validate Required field + if (!this.invitroTestAgent.testAgent) { + this.requiredFieldMissingArray[0].testAgent = true; + } } else if (worksheet[cellKey].v.trim() === 'Test Agent Approval ID/UNII') { this.invitroTestAgent.testAgentApprovalId = this.getValue(worksheet[cellKeyValue]); } else if (worksheet[cellKey].v.trim() === 'Test Agent CAS Number') { @@ -460,9 +510,12 @@ export class InvitroPharmacologyScreeningDataImportComponent implements OnInit { } // for loop Column } // for loop Row + if (this.invitroTestAgent.testAgent) { + this.getSubstanceByNameExactMatch(this.invitroTestAgent.testAgent); + } } - createInvitroBatchNumber(workbook: XLSX.WorkBook) { + getInvitroBatchNumber(workbook: XLSX.WorkBook) { // Read the Fourth Excel Spreadsheet, the worksheet index starts with 0. // Read Sheet "3. Test Agent,Batch Number" const worksheetName = workbook.SheetNames[3]; @@ -490,8 +543,13 @@ export class InvitroPharmacologyScreeningDataImportComponent implements OnInit { var cellKeyValue = XLSX.utils.encode_cell({ r: R, c: C + 1 }); if (worksheet[cellKey].v) { - if (worksheet[cellKey].v.trim() === 'Batch Number') { + if (worksheet[cellKey].v.trim() === 'Batch Number *') { this.invitroResultInfo.batchNumber = this.getValue(worksheet[cellKeyValue]); + + // ** Validate Required field + if (!this.invitroResultInfo.batchNumber) { + this.requiredFieldMissingArray[0].batchNumber = true; + } } } // if value is not null } // else @@ -501,7 +559,7 @@ export class InvitroPharmacologyScreeningDataImportComponent implements OnInit { } - createInvitroControls(workbook: XLSX.WorkBook) { + getInvitroControls(workbook: XLSX.WorkBook) { // Read the Second Excel Spreadsheet, the worksheet index starts with 0. // Read Sheet "4. Assay Controls" const worksheetName = workbook.SheetNames[4]; @@ -518,26 +576,41 @@ export class InvitroPharmacologyScreeningDataImportComponent implements OnInit { element["externalAssayId"] = this.replaceUndefinedValue(element["External Assay ID *"]); element["assayId"] = this.replaceUndefinedValue(element["Assay ID"]); element["control"] = this.replaceUndefinedValue(element["Control Substance *"]); + element["controlApprovalId"] = this.replaceUndefinedValue(element["Control Substance Approval ID"]); element["controlType"] = this.replaceUndefinedValue(element["Type of Control"]); - element["controlValueType"] = this.replaceUndefinedValue(element["Control Value Type"]); - element["controlValue"] = this.replaceUndefinedValue(element["Control Value"]); - element["controlValueUnits"] = this.replaceUndefinedValue(element["Control Value Units"]); + element["controlResultType"] = this.replaceUndefinedValue(element["Control Result Type"]); + element["controlReferenceValue"] = this.replaceUndefinedValue(element["Control Reference Value"]); + element["controlReferenceValueUnits"] = this.replaceUndefinedValue(element["Control Reference Value Units"]); // Delete Excel Object key delete element["External Assay Source *"]; delete element["External Assay ID *"]; delete element["Assay ID"]; - delete element["Control Substance"]; + delete element["Control Substance *"]; + delete element["Control Substance Approval ID"]; delete element["Type of Control"]; - delete element["Control Value Type"]; - delete element["Control Value"]; - delete element["Control Value Units"]; + delete element["Control Result Type"]; + delete element["Control Reference Value"]; + delete element["Control Reference Value Units"]; + + // create a row if it is empty + if (this.requiredFieldMissingArray[index] == null) { + this.requiredFieldMissingArray[index] = {}; + } + + // ** Validate Required field + if (!element["externalAssaySource"]) { + this.requiredFieldMissingArray[index].controlExternalAssaySource = true; + } + if (!element["externalAssayId"]) { + this.requiredFieldMissingArray[index].controlExternalAssayId = true; + } } }); } - createInvitroResults(workbook: XLSX.WorkBook) { + getInvitroResults(workbook: XLSX.WorkBook) { // Read the Second Excel Spreadsheet, the worksheet index starts with 0. // Read Sheet "5. Assay Results" const worksheetName = workbook.SheetNames[5]; @@ -550,9 +623,9 @@ export class InvitroPharmacologyScreeningDataImportComponent implements OnInit { this.invitroResultsTemp.forEach((element, index) => { if (element) { + element["assaySet"] = this.replaceUndefinedValue(element["Assay Set"]); element["externalAssaySource"] = this.replaceUndefinedValue(element["External Assay Source *"]); element["externalAssayId"] = this.replaceUndefinedValue(element["External Assay ID *"]); - element["externalAssayUrl"] = this.replaceUndefinedValue(element["External Assay URL/Document Link"]); element["assayId"] = this.replaceUndefinedValue(element["Assay ID"]); let testDateNum = this.replaceUndefinedValue(element["Test Date (mm/dd/yyyy)"]); element["testAgentConcentration"] = this.replaceUndefinedValue(element["Test Agent Concentration"]); @@ -578,10 +651,31 @@ export class InvitroPharmacologyScreeningDataImportComponent implements OnInit { element["testDate"] = testDate; } + // Convert Plasma Protein Added from "YES" or "NO" to boolean true and false + if (element["plasmaProteinAdded"]) { + if (element["plasmaProteinAdded"] === "YES") { + element["plasmaProteinAdded"] = true; + } else if (element["plasmaProteinAdded"] === "NO") { + element["plasmaProteinAdded"] = false; + } + } + + // create a row if it is empty + if (this.requiredFieldMissingArray[index] == null) { + this.requiredFieldMissingArray[index] = {}; + } + // ** Validate Required field + if (!element["externalAssaySource"]) { + this.requiredFieldMissingArray[index].resultExternalAssaySource = true; + } + if (!element["externalAssayId"]) { + this.requiredFieldMissingArray[index].resultExternalAssayId = true; + } + // Delete Excel Object key + delete element["Assay Set"]; delete element["External Assay Source *"]; delete element["External Assay ID *"]; - delete element["External Assay URL/Document Link"]; delete element["Assay ID"]; delete element["Test Date (mm/dd/yyyy)"]; delete element["Test Agent Concentration"]; @@ -614,7 +708,13 @@ export class InvitroPharmacologyScreeningDataImportComponent implements OnInit { return (value === undefined || value == null || value.length <= 0) ? "" : value; } - validateExcelImportData() { + validate() { + + //this.checkControlAssayFoundInDatabase(); + + this.checkResultAssayFoundInDatabase(); + + /* let foundallAssays = 'true'; // Validate if Assay already Exists into the database @@ -649,6 +749,106 @@ export class InvitroPharmacologyScreeningDataImportComponent implements OnInit { // Enable "Import into the Database" button this.disableImportButton = 'false'; } + */ + + } + + /* + checkControlAssayFoundInDatabase() { + let foundallAssays = 'true'; + + // Validate if Assay already Exists into the database + this.invitroControlsTemp.forEach((control, index) => { + if (control.externalAssaySource && control.externalAssayId) { + + const invitroSubscribe = this.invitroPharmacologyService.getAssayByExternalAssay(control.externalAssaySource, control.externalAssayId).subscribe(assay => { + if (assay) { + // Assay Found in the Database + control.assayFoundInDb = 'true'; + } + else { + // Assay NOT Found in the Database + control.assayFoundInDb = 'false'; + foundallAssays = 'false'; + } + }, error => { + control.assayFoundInDb = 'Error getting Assay'; + console.log("Import Screeing data - error getting Assay"); + } + ); // subscribe + + this.subscriptions.push(invitroSubscribe); + } else { + foundallAssays = 'false'; + } + + }); + + // Enable "Import into the Database" button + this.disableImportButton = 'false'; + } + */ + + checkResultAssayFoundInDatabase() { + let foundallAssays = 'true'; + this.resultMessage = ''; + + // Validate if Assay already Exists into the database + this.invitroResultsTemp.forEach((result, index) => { + + if (result.externalAssaySource && result.externalAssayId) { + + this.resultMessage = 'Checking Assays in the database ...'; + + /* + // CONTROL ASSAY CHECK, check if Result Assays match control Assays + this.invitroControlsTemp.forEach(ctrl => { + if (ctrl) { + if ((ctrl.externalAssaySource) && (ctrl.externalAssayId)) { + + // if Result and Control Assays match + if ((ctrl.externalAssaySource.externalAssaySource === result.externalAssaySource) + && (ctrl.externalAssayId === result.externalAssayId)) { + controlAssayMatch = true; + } + } + } // if control object exists + }); // control loop + */ + + const invitroSubscribe = this.invitroPharmacologyService.getAssayByExternalAssay(result.externalAssaySource, result.externalAssayId).subscribe(assay => { + if (assay) { + // Assay Found in the Database + result.assayFoundInDb = 'true'; + + this.createNewScreeningData(assay, result); + } + else { + // Assay NOT Found in the Database + result.assayFoundInDb = 'false'; + foundallAssays = 'false'; + } + + if (this.invitroResultsTemp.length === (index + 1)) { + this.resultMessage = ''; + } + + }, error => { + result.assayFoundInDb = 'Error getting Assay'; + console.log("Import Screeing data - error getting Assay"); + } + ); // subscribe + + this.subscriptions.push(invitroSubscribe); + } else { + foundallAssays = 'false'; + } + + }); + + // Enable "Import into the Database" button + this.disableImportButton = 'false'; + } createNewScreeningData(assay: InvitroAssayInformation, resultElement: any) { // Create new screening object @@ -659,7 +859,8 @@ export class InvitroPharmacologyScreeningDataImportComponent implements OnInit { screening.screeningImportFileName = importfilename; - // Invitro Result + // Create new Invitro Control and Result + screening.invitroControls = this.createNewInvitroControl(resultElement); screening.invitroAssayResult = this.createInvitroResult(resultElement); // Push screening to Assay @@ -687,6 +888,39 @@ export class InvitroPharmacologyScreeningDataImportComponent implements OnInit { return newObject; } + createNewInvitroControl(resultObject: any): any { + let invitroControls: Array = []; + + let tempResultObject = _.cloneDeep(resultObject); + + // CONTROL ASSAY CHECK, check if Result Assays match control Assays + this.invitroControlsTemp.forEach(ctrl => { + if (ctrl) { + if ((ctrl.externalAssaySource) && (ctrl.externalAssayId)) { + // if Result and Control Assays match + if ((ctrl.externalAssaySource === tempResultObject.externalAssaySource) + && (ctrl.externalAssayId === tempResultObject.externalAssayId)) { + let tempObject = _.cloneDeep(ctrl); + + // Delete the keys that not needed + delete tempObject.externalAssaySource; + delete tempObject.externalAssayId; + delete tempObject.assayId; + delete tempObject.assayFoundInDb; + + // Create new control object + let newObject: InvitroControl = {}; + newObject = tempObject; + + invitroControls.push(newObject); + } + } + } // if control object exists + }); // control loop + + return invitroControls; + } + createInvitroResult(object: any): any { let tempObject = _.cloneDeep(object); @@ -770,6 +1004,98 @@ export class InvitroPharmacologyScreeningDataImportComponent implements OnInit { } */ + getSubstanceByNameExactMatch(ingredientName: string, fieldName?: string) { + let found = false; + + const substanceSubscribe = this.generalService.getSubstanceByNameExactMatch(ingredientName).subscribe(response => { + if (response) { + if (response.content && response.content.length > 0) { + + // Loop through the search results and if the Substance/Ingredient name is same as name in the search + // result, select that substance + let substances = response.content; + for (let i = 0; i < substances.length; i++) { + let substance = substances[i]; + if (substance) { + if (substance.names && substance.names.length > 0) { + + substance.names.forEach(nameObj => { + if (nameObj && nameObj.name === ingredientName) { + + found = true; + + this.invitroTestAgent.testAgentSubstanceUuid = substance.uuid; + + // let substanceKey = this.generalService.getSubstanceKeyBySubstanceResolver(substance, this.substanceKeyTypeForInvitroPharmacologyConfig); + + /* + if (fieldName == this.TARGET_NAME) { + + element["targetNameSubstanceUuid"] = substance.uuid; + element["targetNameSubstanceKey"] = substanceKey; + element["targetNameSubstanceKeyType"] = this.substanceKeyTypeForInvitroPharmacologyConfig; + + if ((element["targetNameApprovalId"]) && (element["targetNameApprovalId"] !== substance.approvalID)) { + this.setValidationMessage(this.TARGET_NAME + ' Approval ID "' + element["targetNameApprovalId"] + '" in Excel file does not match with Approval ID "' + substance.approvalID + '" in the database. Please fix in the Excel file and then import again', validationMessages, index); + } + } + else if (fieldName == this.HUMAN_HOMOLOG_TARGET) { + element["humanHomologTargetSubstanceKey"] = substanceKey; + element["humanHomologTargetSubstanceKeyType"] = this.substanceKeyTypeForInvitroPharmacologyConfig; + + if ((element["humanHomologTargetApprovalId"]) && (element["humanHomologTargetApprovalId"] !== substance.approvalID)) { + this.setValidationMessage(this.HUMAN_HOMOLOG_TARGET + ' Approval ID "' + element["humanHomologTargetApprovalId"] + '" in Excel file does not match with Approval ID "' + substance.approvalID + '" in the database. Please fix in the Excel file and then import again', validationMessages, index); + } + } + else if (fieldName == this.LIGAND_SUBSTRATE) { + element["ligandSubstrateSubstanceKey"] = substanceKey; + element["ligandSubstrateSubstanceKeyType"] = this.substanceKeyTypeForInvitroPharmacologyConfig; + + if ((element["ligandSubstrateApprovalId"]) && (element["ligandSubstrateApprovalId"] !== substance.approvalID)) { + this.setValidationMessage(this.LIGAND_SUBSTRATE + ' Approval ID "' + element["ligandSubstrateApprovalId"] + '" in Excel file does not match with Approval ID "' + substance.approvalID + '" in the database. Please fix in the Excel file and then import again', validationMessages, index); + } + } */ + + } // if names match + }); // substance names for loop + } // if names exist + } // if substance exists + } // substances for loop + + /* + if (found == false) { + this.setValidationMessage(fieldName + ' "' + ingredientName + '" does not exist in the database. Please register this substance first and then import again', validationMessages, index); + } + + if (fieldName == this.TARGET_NAME) { + this.targetNameCheckCompleted = true; + } else if (fieldName == this.HUMAN_HOMOLOG_TARGET) { + this.humanHomologCheckCompleted = true; + } else if (fieldName == this.LIGAND_SUBSTRATE) { + this.ligandCheckCompleted = true; + } */ + + // Enable Database Import button + //if ( this.targetNameCheckCompleted && this.targetNameCheckCompleted && this.targetNameCheckCompleted) { + // this.disableImportButton = "false"; + // } + + } // if content > 0 + else { + // this.setValidationMessage(fieldName + ' "' + ingredientName + '" does not exist in the database. Please register this substance first and then import again', validationMessages, index); + } + } // if response + else { + // this.setValidationMessage(fieldName + ' "' + ingredientName + '" does not exist in the database. Please register this substance first and then import again', validationMessages, index); + } + }, error => { + // this.setValidationMessage(fieldName + ' "' + ingredientName + '" does not exist in the database. Please register this substance first and then import again', validationMessages, index); + }, () => { + + }); + this.subscriptions.push(substanceSubscribe); + } + importAssayJSONIntoDatabase() { this.loadingService.setLoading(true); @@ -780,6 +1106,7 @@ export class InvitroPharmacologyScreeningDataImportComponent implements OnInit { let firstAssayToSave = this.assayToSave[0]; // Set Reference to Result Information Object + this.invitroResultInfo.invitroReferences = this.invtroReferences; this.invitroResultInfo.invitroLaboratory = this.invitroLaboratory; this.invitroResultInfo.invitroSponsor = this.invitroSponsor; this.invitroResultInfo.invitroSponsorReport = this.invitroSponsorReport; @@ -815,7 +1142,7 @@ export class InvitroPharmacologyScreeningDataImportComponent implements OnInit { assay.invitroAssayScreenings.forEach(screening => { // Assign the first invitroAssayResultInformation here. - // screening.invitroAssayResultInformation = savedResultInfo; + // screening.invitroAssayResultInformation = savedResultInfo; // screening.invitroAssayResultInformation = {}; // screening.invitroAssayResultInformation.id = savedResultInfo.id; @@ -823,9 +1150,9 @@ export class InvitroPharmacologyScreeningDataImportComponent implements OnInit { }); - assay.invitroAssayScreenings[assay.invitroAssayScreenings.length - 1].invitroAssayResultInformation = savedResultInfo; - //assay.invitroAssayScreenings[assay.invitroAssayScreenings.length - 1].invitroAssayResultInformation = {}; - //assay.invitroAssayScreenings[assay.invitroAssayScreenings.length - 1].invitroAssayResultInformation.id = savedResultInfo.id; + assay.invitroAssayScreenings[assay.invitroAssayScreenings.length - 1].invitroAssayResultInformation = savedResultInfo; + //assay.invitroAssayScreenings[assay.invitroAssayScreenings.length - 1].invitroAssayResultInformation = {}; + //assay.invitroAssayScreenings[assay.invitroAssayScreenings.length - 1].invitroAssayResultInformation.id = savedResultInfo.id; // Assign the assay to service assay this.invitroPharmacologyService.assay = assay; @@ -873,7 +1200,7 @@ export class InvitroPharmacologyScreeningDataImportComponent implements OnInit { json = this.assayToSave; } - let data = {jsonData: json, jsonFilename: jsonFilename}; + let data = { jsonData: json, jsonFilename: jsonFilename }; const dialogRef = this.dialog.open(JsonDialogFdaComponent, { width: '90%', @@ -886,6 +1213,40 @@ export class InvitroPharmacologyScreeningDataImportComponent implements OnInit { this.subscriptions.push(dialogSubscription); } + isNumber(str: any): boolean { + if (str) { + const num = Number(str); + const nan = isNaN(num); + return !nan; + } + return false; + } + + validateDate(dateinput: any): boolean { + let isValid = true; + if ((dateinput !== null) && (dateinput.length > 0)) { + if ((dateinput.length < 8) || (dateinput.length > 10)) { + return false; + } + const split = dateinput.split('/'); + if (split.length !== 3 || (split[0].length < 1 || split[0].length > 2) || + (split[1].length < 1 || split[1].length > 2) || split[2].length !== 4) { + return false; + } + if (split.length === 3) { + const comstring = split[0] + split[1] + split[2]; + for (let i = 0; i < split.length; i++) { + const valid = this.isNumber(split[i]); + if (valid === false) { + isValid = false; + break; + } + } + } + } + return isValid; + } + scrub(oldraw: any): any { const old = oldraw; diff --git a/src/app/fda/invitro-pharmacology/model/invitro-pharmacology.model.ts b/src/app/fda/invitro-pharmacology/model/invitro-pharmacology.model.ts index 88728cee8..6bd4d6ca0 100644 --- a/src/app/fda/invitro-pharmacology/model/invitro-pharmacology.model.ts +++ b/src/app/fda/invitro-pharmacology/model/invitro-pharmacology.model.ts @@ -179,7 +179,7 @@ export interface InvitroSponsorSubmitter { modifiedBy?: string; internalVersion?: number; sponsorReportSubmitterName?: string; - sponsorRepoortSubmitterTitle?: string; + sponsorReportSubmitterTitle?: string; sponsorReportSubmitterAffiliation?: string; sponsorReportSubmitterEmail?: string; sponsorReportSubmitterPhoneNumber?: string; diff --git a/src/app/fda/service/general.service.ts b/src/app/fda/service/general.service.ts index a48f46a94..50e02e16e 100644 --- a/src/app/fda/service/general.service.ts +++ b/src/app/fda/service/general.service.ts @@ -156,6 +156,38 @@ export class GeneralService extends BaseHttpService { return this.http.get>(url, options); } + getSubstanceByNameExactMatch( + searchTerm?: string, + getFacets?: boolean, + facets?: FacetParam + ): Observable> { + let params = new FacetHttpParams(); + + let url = this.apiBaseUrl + 'substances/'; + + let nameSearchTerm = 'root_names_name:"^' + searchTerm + '$"'; + + if (searchTerm) { + params = params.append('q', nameSearchTerm); + params = params.append('view', 'full'); + } + + if (searchTerm != null || getFacets === true) { + url += 'search'; + } + + if (facets != null) { + let showDeprecated = false; + params = params.appendFacetParams(facets, showDeprecated); + } + + const options = { + params: params + }; + + return this.http.get>(url, options); + } + getSearchCount(substanceUuid: string): Observable { const url = `${this.configService.configData.apiBaseUrl}api/v1/searchcounts/` + substanceUuid; return this.http.get(url) @@ -166,7 +198,6 @@ export class GeneralService extends BaseHttpService { ); } - getProductFacets(): Observable { let url: string; // url = `${this.configService.configData.apiBaseUrl}api/v1/products/search/@facets?wait=false&kind=gov.hhs.gsrs.products.product.models.Product&skip=0&fdim=200&sideway=true&top=14448&fskip=0&fetch=100&termfilter=SubstanceDeprecated%3Afalse`; From 66a237327faef321f4c65c84cec2f040dd525bfb Mon Sep 17 00:00:00 2001 From: Mitch Miller Date: Tue, 2 Sep 2025 12:49:04 -0400 Subject: [PATCH 082/408] completing merge by removing duplicate import --- src/app/fda/product/product-form/product-form.component.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/app/fda/product/product-form/product-form.component.ts b/src/app/fda/product/product-form/product-form.component.ts index d50c392d6..46b0bf320 100644 --- a/src/app/fda/product/product-form/product-form.component.ts +++ b/src/app/fda/product/product-form/product-form.component.ts @@ -4,7 +4,6 @@ import { MatDialog } from '@angular/material/dialog'; import { MatDatepickerInputEvent } from '@angular/material/datepicker'; import { Title, DomSanitizer, SafeUrl } from '@angular/platform-browser'; import { Subscription } from 'rxjs'; -import { Title } from '@angular/platform-browser'; import { take } from 'rxjs/operators'; import { OverlayContainer } from '@angular/cdk/overlay'; import * as moment from 'moment'; From dc1b8abb5e4c835ad20ab9717c995f03c37bc92e Mon Sep 17 00:00:00 2001 From: alx652 <58182629+alx652@users.noreply.github.com> Date: Tue, 2 Sep 2025 20:03:05 -0400 Subject: [PATCH 083/408] Update config.json --- src/app/fda/config/config.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/fda/config/config.json b/src/app/fda/config/config.json index 7d8e32298..ce4857068 100644 --- a/src/app/fda/config/config.json +++ b/src/app/fda/config/config.json @@ -912,7 +912,7 @@ "DASH INDICATION", "DEA NO.", "DME Reactions", - "DRUG BANK", + "DRUGBANK", "DRUG CENTRAL", "DSLD", "EC (ENZYME CLASS)", From 0920d7cfaba19f6ef41a9e2bb20ed5df8c148f7a Mon Sep 17 00:00:00 2001 From: alx652 <58182629+alx652@users.noreply.github.com> Date: Tue, 2 Sep 2025 20:06:06 -0400 Subject: [PATCH 084/408] Update config.json --- src/app/core/config/config.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/core/config/config.json b/src/app/core/config/config.json index ed5e90ecd..bccb0d7cb 100644 --- a/src/app/core/config/config.json +++ b/src/app/core/config/config.json @@ -613,7 +613,7 @@ }, { "category": "Codes", - "facets": ["ATCC", "BDNUM", "BIOLOGIC SUBSTANCE CLASSIFICATION CODE", "CAS", "CAYMAN", "CERES", "CFR", "CFSAN PSEUDO CAS", "ChEMBL", "CLINICAL_TRIALS.GOV", "Code System", "CODEX ALIMENTARIUS (GSFA)", "COSMETIC INGREDIENT REVIEW (CIR)", "DASH INDICATION", "DEA NO.", "DME Reactions", "DRUG BANK", "DRUG CENTRAL", "DSLD", "EC (ENZYME CLASS)", "EC SCIENTIFIC COMMITTEE ON CONSUMER SAFETY OPINION", "ECHA (EC/EINECS)", "EMA ASSESSMENT REPORTS", "EMA VETERINARY ASSESSMENT REPORTS", "EPA CompTox", "EPA PESTICIDE CODE", "EU CLINICAL TRIALS REGISTER", "EU FOOD ADDITIVES", "EU-Orphan Drug", "EVMPD", "FARM SUBSTANCE ID", "FDA ORPHAN DRUG", "FDA UNII", "Food Contact Sustance Notif, (FCN No.)", "GENE", "GRIN", "HEALTH -CANADA NHP INGREDIENT MONOGRAPH", "HEALTH-CANADA NHP INGREDIENT RECORD", "HSDB", "IARC", "INCB IDS CODE", "INN", "INS", "ITIS", "IUPHAR", "JAPANESE REVIEW", "JECFA EVALUATION", "JECFA MONOGRAPH", "JMPR-PESTICIDE RESIDUE", "KEGG", "LactMed", "LIVERTOX", "LOINC", "MANUFACTURER PRODUCT INFORMATION", "MERCK INDEX", "MESH", "MIRBASE", "MPNS", "NCBI TAXONOMY", "NCI_THESAURUS", "NDF-RT", "NSC", "Other", "PFAF", "PHAROS", "PROTEIN ID", "PUBCHEM", "RXCUI", "STARI", "SUPERSEDED_BD_NUM", "SWGDRUG", "UCSF-FDA TRANSPORTAL", "UNII", "UNIPROT", "USDA PLANTS", "USP_CATALOG", "USP-HMC", "WEB RESOURCE", "WHO INTERNATIONAL PHARMACOPEIA", "WHO INTERNATIONAL PHARMACPOEIA", "WHO-ATC", "WHO-ESSENTIAL MEDICINES LIST", "WHO-SDG", "WHO-SDG Level 1", "WHO-SDG Level 2", "WHO-VATC", "WIKIPEDIA", "YELLOW LIST"] + "facets": ["ATCC", "BDNUM", "BIOLOGIC SUBSTANCE CLASSIFICATION CODE", "CAS", "CAYMAN", "CERES", "CFR", "CFSAN PSEUDO CAS", "ChEMBL", "CLINICAL_TRIALS.GOV", "Code System", "CODEX ALIMENTARIUS (GSFA)", "COSMETIC INGREDIENT REVIEW (CIR)", "DASH INDICATION", "DEA NO.", "DME Reactions", "DRUGBANK", "DRUG CENTRAL", "DSLD", "EC (ENZYME CLASS)", "EC SCIENTIFIC COMMITTEE ON CONSUMER SAFETY OPINION", "ECHA (EC/EINECS)", "EMA ASSESSMENT REPORTS", "EMA VETERINARY ASSESSMENT REPORTS", "EPA CompTox", "EPA PESTICIDE CODE", "EU CLINICAL TRIALS REGISTER", "EU FOOD ADDITIVES", "EU-Orphan Drug", "EVMPD", "FARM SUBSTANCE ID", "FDA ORPHAN DRUG", "FDA UNII", "Food Contact Sustance Notif, (FCN No.)", "GENE", "GRIN", "HEALTH -CANADA NHP INGREDIENT MONOGRAPH", "HEALTH-CANADA NHP INGREDIENT RECORD", "HSDB", "IARC", "INCB IDS CODE", "INN", "INS", "ITIS", "IUPHAR", "JAPANESE REVIEW", "JECFA EVALUATION", "JECFA MONOGRAPH", "JMPR-PESTICIDE RESIDUE", "KEGG", "LactMed", "LIVERTOX", "LOINC", "MANUFACTURER PRODUCT INFORMATION", "MERCK INDEX", "MESH", "MIRBASE", "MPNS", "NCBI TAXONOMY", "NCI_THESAURUS", "NDF-RT", "NSC", "Other", "PFAF", "PHAROS", "PROTEIN ID", "PUBCHEM", "RXCUI", "STARI", "SUPERSEDED_BD_NUM", "SWGDRUG", "UCSF-FDA TRANSPORTAL", "UNII", "UNIPROT", "USDA PLANTS", "USP_CATALOG", "USP-HMC", "WEB RESOURCE", "WHO INTERNATIONAL PHARMACOPEIA", "WHO INTERNATIONAL PHARMACPOEIA", "WHO-ATC", "WHO-ESSENTIAL MEDICINES LIST", "WHO-SDG", "WHO-SDG Level 1", "WHO-SDG Level 2", "WHO-VATC", "WIKIPEDIA", "YELLOW LIST"] } ] } From 0bc02331db4ed86be55656c4b7b2f7d334b17653 Mon Sep 17 00:00:00 2001 From: Jaroslav Iha Date: Tue, 9 Sep 2025 13:57:39 +0200 Subject: [PATCH 085/408] add: svg saving for stepview --- src/app/core/substance-ssg4m/model/substance-ssg4m.model.ts | 3 +-- .../core/substance-ssg4m/substance-ssg4m-form.component.ts | 5 ++++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/app/core/substance-ssg4m/model/substance-ssg4m.model.ts b/src/app/core/substance-ssg4m/model/substance-ssg4m.model.ts index 9a11ebdfd..b31d7afb0 100644 --- a/src/app/core/substance-ssg4m/model/substance-ssg4m.model.ts +++ b/src/app/core/substance-ssg4m/model/substance-ssg4m.model.ts @@ -1,4 +1,3 @@ - export interface Ssg4mSyntheticPathway { createdBy?: string; modifiedBy?: string; @@ -16,6 +15,7 @@ export interface Ssg4mSyntheticPathway { printSbstncUuid?: string; printSbstncPrfrdNm?: string; sbmsnImage?: string; + stepViewImage?: string; ssg4mSyntheticPathwayDetailsList?: Array; fileUrl?: string; } @@ -31,4 +31,3 @@ export interface Ssg4mSyntheticPathwayDetail { sbstncReactnSectNm?: string; sbstncRoleNm?: string; } - diff --git a/src/app/core/substance-ssg4m/substance-ssg4m-form.component.ts b/src/app/core/substance-ssg4m/substance-ssg4m-form.component.ts index 975f8ea2a..5f193253e 100644 --- a/src/app/core/substance-ssg4m/substance-ssg4m-form.component.ts +++ b/src/app/core/substance-ssg4m/substance-ssg4m-form.component.ts @@ -41,6 +41,7 @@ import { JsonDialogComponent } from '@gsrs-core/substance-form/json-dialog/json- import { SubstanceSsg4mService } from './substance-ssg4m-form.service'; import { environment } from '@gsrs-core/../../environments/environment'; import { Ssg4mSyntheticPathway } from './model/substance-ssg4m.model'; +import { toSvg } from 'html-to-image'; @Component({ selector: 'app-substance-ssg4m-form', @@ -993,7 +994,7 @@ export class SubstanceSsg4ManufactureFormComponent implements OnInit, AfterViewI //This is a hacky placeholder way to force viz //TODO finish this const ssgjs = JSON.stringify(this.substanceFormService.cleanSubstance()); - window["schemeUtil"].onFinishedLayout = (svg) => { + window["schemeUtil"].onFinishedLayout = async (svg) => { window["schemeUtil"].onFinishedLayout = (svg) => { }; // if New Record, initialize object @@ -1008,6 +1009,8 @@ export class SubstanceSsg4ManufactureFormComponent implements OnInit, AfterViewI // Save SVG as Clob this.ssg4mSyntheticPathway.sbmsnImage = document.querySelector("#scheme-viz-view").innerHTML; + this.ssg4mSyntheticPathway.stepViewImage= await toSvg(document.querySelector('app-ssg4m-scheme-view') as HTMLElement); + // After submitting Save button, the UI waits for 5 seconds to see if it gets a response. // after 5 seconds it displays a warning on the top of the UI form. setTimeout(() => { From 9d55408eb49c742072cec496eebd272a9abf2158 Mon Sep 17 00:00:00 2001 From: Jaroslav Iha Date: Wed, 10 Sep 2025 15:16:23 +0200 Subject: [PATCH 086/408] svg save test --- .../substance-ssg4m-form.component.ts | 22 ++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/src/app/core/substance-ssg4m/substance-ssg4m-form.component.ts b/src/app/core/substance-ssg4m/substance-ssg4m-form.component.ts index 5f193253e..95a0cf00c 100644 --- a/src/app/core/substance-ssg4m/substance-ssg4m-form.component.ts +++ b/src/app/core/substance-ssg4m/substance-ssg4m-form.component.ts @@ -1009,7 +1009,27 @@ export class SubstanceSsg4ManufactureFormComponent implements OnInit, AfterViewI // Save SVG as Clob this.ssg4mSyntheticPathway.sbmsnImage = document.querySelector("#scheme-viz-view").innerHTML; - this.ssg4mSyntheticPathway.stepViewImage= await toSvg(document.querySelector('app-ssg4m-scheme-view') as HTMLElement); + const dataUrl = await toSvg(document.querySelector('app-ssg4m-scheme-view') as HTMLElement); + const commaIndex = dataUrl.indexOf(','); + const encodedSvg = dataUrl.slice(commaIndex + 1); + const downloadLink = document.createElement('a'); + + // 2. Set the href attribute to the data URL + downloadLink.href = dataUrl; + + // 3. Set the download attribute to the desired file name + downloadLink.download = 'img.svg'; + + // 4. Append the link to the document. This is required for Firefox. + document.body.appendChild(downloadLink); + + // 5. Programmatically click the link to initiate the download + downloadLink.click(); + + // 6. Remove the link from the document + document.body.removeChild(downloadLink); + + this.ssg4mSyntheticPathway.stepViewImage = decodeURIComponent(encodedSvg); // After submitting Save button, the UI waits for 5 seconds to see if it gets a response. // after 5 seconds it displays a warning on the top of the UI form. From dc89c02bebcb28780c4e75c288c0275eaa180e08 Mon Sep 17 00:00:00 2001 From: Newatia Date: Wed, 10 Sep 2025 11:22:26 -0400 Subject: [PATCH 087/408] updated product and IVP --- ...harmacology-assay-data-import.component.ts | 136 +++++++------- .../product-form/product-form.component.html | 10 +- .../product-form/product-form.component.ts | 167 +++++++++++++++--- .../product-lot-form.component.html | 6 +- .../product-lot-form.component.scss | 6 +- .../product-lot-form.component.ts | 72 ++++++-- 6 files changed, 290 insertions(+), 107 deletions(-) diff --git a/src/app/fda/invitro-pharmacology/invitro-pharmacology-assay-data-import/invitro-pharmacology-assay-data-import.component.ts b/src/app/fda/invitro-pharmacology/invitro-pharmacology-assay-data-import/invitro-pharmacology-assay-data-import.component.ts index 564c322a3..916adbdb0 100644 --- a/src/app/fda/invitro-pharmacology/invitro-pharmacology-assay-data-import/invitro-pharmacology-assay-data-import.component.ts +++ b/src/app/fda/invitro-pharmacology/invitro-pharmacology-assay-data-import/invitro-pharmacology-assay-data-import.component.ts @@ -419,7 +419,7 @@ export class InvitroPharmacologyAssayDataImportComponent implements OnInit { validate.message = message; validate.messageType = 'ERROR'; validationMessages.push(validate); - + this.importValidateMessageArray[index].valid = false; // Disable Import to Database button @@ -531,88 +531,94 @@ export class InvitroPharmacologyAssayDataImportComponent implements OnInit { let found = false; - const substanceSubscribe = this.generalService.getSubstanceByNameExactMatch(ingredientName).subscribe(response => { - if (response) { - if (response.content && response.content.length > 0) { + if (ingredientName) { + const substanceSubscribe = this.generalService.getSubstanceByNameExactMatch(ingredientName).subscribe(response => { + if (response) { + if (response.content && response.content.length > 0) { + + // Loop through the search results and if the Substance/Ingredient name is same as name in the search + // result, select that substance + let substances = response.content; + for (let i = 0; i < substances.length; i++) { + let substance = substances[i]; + if (substance) { + if (substance.names && substance.names.length > 0) { + + substance.names.forEach(nameObj => { + if (nameObj && nameObj.name === ingredientName.toUpperCase()) { - // Loop through the search results and if the Substance/Ingredient name is same as name in the search - // result, select that substance - let substances = response.content; - for (let i = 0; i < substances.length; i++) { - let substance = substances[i]; - if (substance) { - if (substance.names && substance.names.length > 0) { + found = true; - substance.names.forEach(nameObj => { - if (nameObj && nameObj.name === ingredientName) { + let substanceKey = this.generalService.getSubstanceKeyBySubstanceResolver(substance, this.substanceKeyTypeForInvitroPharmacologyConfig); - found = true; + if (fieldName == this.TARGET_NAME) { + element["targetNameSubstanceUuid"] = substance.uuid; + element["targetNameSubstanceKey"] = substanceKey; + element["targetNameSubstanceKeyType"] = this.substanceKeyTypeForInvitroPharmacologyConfig; - let substanceKey = this.generalService.getSubstanceKeyBySubstanceResolver(substance, this.substanceKeyTypeForInvitroPharmacologyConfig); + if (substance.approvalID) { - if (fieldName == this.TARGET_NAME) { - element["targetNameSubstanceUuid"] = substance.uuid; - element["targetNameSubstanceKey"] = substanceKey; - element["targetNameSubstanceKeyType"] = this.substanceKeyTypeForInvitroPharmacologyConfig; - - if ((element["targetNameApprovalId"]) && (element["targetNameApprovalId"] !== substance.approvalID)) { - this.setValidationMessage(this.TARGET_NAME + ' Approval ID "' + element["targetNameApprovalId"] + '" in Excel file does not match with Approval ID "' + substance.approvalID + '" in the database. Please fix in the Excel file and then import again', validationMessages, index); + } + + if ((element["targetNameApprovalId"]) && (element["targetNameApprovalId"] !== substance.approvalID)) { + this.setValidationMessage(this.TARGET_NAME + ' Approval ID "' + element["targetNameApprovalId"] + '" in Excel file does not match with Approval ID "' + substance.approvalID + '" for "' + ingredientName + '" in the database. Please fix in the Excel file and then import again', validationMessages, index); + } } - } - else if (fieldName == this.HUMAN_HOMOLOG_TARGET) { - element["humanHomologTargetSubstanceKey"] = substanceKey; - element["humanHomologTargetSubstanceKeyType"] = this.substanceKeyTypeForInvitroPharmacologyConfig; - - if ((element["humanHomologTargetApprovalId"]) && (element["humanHomologTargetApprovalId"] !== substance.approvalID)) { - this.setValidationMessage(this.HUMAN_HOMOLOG_TARGET + ' Approval ID "' + element["humanHomologTargetApprovalId"] + '" in Excel file does not match with Approval ID "' + substance.approvalID + '" in the database. Please fix in the Excel file and then import again', validationMessages, index); + else if (fieldName == this.HUMAN_HOMOLOG_TARGET) { + element["humanHomologTargetSubstanceKey"] = substanceKey; + element["humanHomologTargetSubstanceKeyType"] = this.substanceKeyTypeForInvitroPharmacologyConfig; + + if ((element["humanHomologTargetApprovalId"]) && (element["humanHomologTargetApprovalId"] !== substance.approvalID)) { + this.setValidationMessage(this.HUMAN_HOMOLOG_TARGET + ' Approval ID "' + element["humanHomologTargetApprovalId"] + '" in Excel file does not match with Approval ID "' + substance.approvalID + '" for "' + ingredientName + '" in the database. Please fix in the Excel file and then import again', validationMessages, index); + } } - } - else if (fieldName == this.LIGAND_SUBSTRATE) { - element["ligandSubstrateSubstanceKey"] = substanceKey; - element["ligandSubstrateSubstanceKeyType"] = this.substanceKeyTypeForInvitroPharmacologyConfig; - - if ((element["ligandSubstrateApprovalId"]) && (element["ligandSubstrateApprovalId"] !== substance.approvalID)) { - this.setValidationMessage(this.LIGAND_SUBSTRATE + ' Approval ID "' + element["ligandSubstrateApprovalId"] + '" in Excel file does not match with Approval ID "' + substance.approvalID + '" in the database. Please fix in the Excel file and then import again', validationMessages, index); + else if (fieldName == this.LIGAND_SUBSTRATE) { + element["ligandSubstrateSubstanceKey"] = substanceKey; + element["ligandSubstrateSubstanceKeyType"] = this.substanceKeyTypeForInvitroPharmacologyConfig; + + if ((element["ligandSubstrateApprovalId"]) && (element["ligandSubstrateApprovalId"] !== substance.approvalID)) { + this.setValidationMessage(this.LIGAND_SUBSTRATE + ' Approval ID "' + element["ligandSubstrateApprovalId"] + '" in Excel file does not match with Approval ID "' + substance.approvalID + '" for "' + ingredientName + '" in the database. Please fix in the Excel file and then import again', validationMessages, index); + } } } - } - }); // substance names for loop - } // if names exist - } // if substance exists - } // substances for loop + }); // substance names for loop + } // if names exist + } // if substance exists + } // substances for loop - if (found == false) { - this.setValidationMessage(fieldName + ' "' + ingredientName + '" does not exist in the database. Please register this substance first and then import again', validationMessages, index); - } + if (found == false) { + this.setValidationMessage(fieldName + ' "' + ingredientName + '" does not exist in the database. Please register this substance first and then import again', validationMessages, index); + } - if (fieldName == this.TARGET_NAME) { - this.targetNameCheckCompleted = true; - } else if (fieldName == this.HUMAN_HOMOLOG_TARGET) { - this.humanHomologCheckCompleted = true; - } else if (fieldName == this.LIGAND_SUBSTRATE) { - this.ligandCheckCompleted = true; - } + if (fieldName == this.TARGET_NAME) { + this.targetNameCheckCompleted = true; + } else if (fieldName == this.HUMAN_HOMOLOG_TARGET) { + this.humanHomologCheckCompleted = true; + } else if (fieldName == this.LIGAND_SUBSTRATE) { + this.ligandCheckCompleted = true; + } - // Enable Database Import button - if ( this.targetNameCheckCompleted && this.targetNameCheckCompleted && this.targetNameCheckCompleted) { - // this.disableImportButton = "false"; - } + // Enable Database Import button + if (this.targetNameCheckCompleted && this.targetNameCheckCompleted && this.targetNameCheckCompleted) { + // this.disableImportButton = "false"; + } - } // if content > 0 + } // if content > 0 + else { + this.setValidationMessage(fieldName + ' "' + ingredientName + '" does not exist in the database. Please register this substance first and then import again', validationMessages, index); + } + } // if response else { this.setValidationMessage(fieldName + ' "' + ingredientName + '" does not exist in the database. Please register this substance first and then import again', validationMessages, index); } - } // if response - else { + }, error => { this.setValidationMessage(fieldName + ' "' + ingredientName + '" does not exist in the database. Please register this substance first and then import again', validationMessages, index); - } - }, error => { - this.setValidationMessage(fieldName + ' "' + ingredientName + '" does not exist in the database. Please register this substance first and then import again', validationMessages, index); - }, () => { - + }, () => { - }); - this.subscriptions.push(substanceSubscribe); + + }); + this.subscriptions.push(substanceSubscribe); + } } showJSON(): void { diff --git a/src/app/fda/product/product-form/product-form.component.html b/src/app/fda/product/product-form/product-form.component.html index 9e8b54b58..cea27dd91 100644 --- a/src/app/fda/product/product-form/product-form.component.html +++ b/src/app/fda/product/product-form/product-form.component.html @@ -172,14 +172,14 @@
    Effective Date (mm/dd/yyyy) - + End Date (mm/dd/yyyy) - + @@ -577,14 +577,14 @@ Start Marketing Date (mm/dd/yyyy) - + End Marketing Date (mm/dd/yyyy) - + @@ -775,7 +775,7 @@ Effective Time (mm/dd/yyyy) - + diff --git a/src/app/fda/product/product-form/product-form.component.ts b/src/app/fda/product/product-form/product-form.component.ts index 5602781b6..a45de3bec 100644 --- a/src/app/fda/product/product-form/product-form.component.ts +++ b/src/app/fda/product/product-form/product-form.component.ts @@ -1,4 +1,4 @@ -import { Component, OnInit, AfterViewInit, OnDestroy, ViewEncapsulation } from '@angular/core'; +import { Component, OnInit, AfterViewInit, OnDestroy } from '@angular/core'; import { ActivatedRoute, Router } from '@angular/router'; import { MatDialog } from '@angular/material/dialog'; import { MatDatepickerInputEvent } from '@angular/material/datepicker'; @@ -35,6 +35,10 @@ export class ProductFormComponent implements OnInit, AfterViewInit, OnDestroy { /* Array data type */ private subscriptions: Array = []; + startMarketingDate: any[][] = []; + endMarketingDate: any[][] = []; + effectiveTime: any[][] = []; + validationMessages: Array = []; provenanceFieldMessage: Array = []; effectiveTimeMessage: any[][] = []; @@ -54,10 +58,14 @@ export class ProductFormComponent implements OnInit, AfterViewInit, OnDestroy { copy: string; submissionMessage: string; jsonFileName: string; - + /* number data type */ id?: number; + /* Date data type */ + effectiveDate: Date; + endDate: Date; + /* boolean data type */ isAdmin = false; isLoading = true; @@ -87,6 +95,9 @@ export class ProductFormComponent implements OnInit, AfterViewInit, OnDestroy { this.loadingService.setLoading(true); this.overlayContainer = this.overlayContainerService.getContainerElement(); this.username = this.authService.getUser(); + + this.initializeDateFieldArray(); + const routeSubscription = this.activatedRoute .params .subscribe(params => { @@ -154,6 +165,15 @@ export class ProductFormComponent implements OnInit, AfterViewInit, OnDestroy { expanded = !expanded; } + initializeDateFieldArray() { + // Assign date field arrays to empty object + for (let i = 0; i < 10; i++) { + this.effectiveTime[i] = []; + this.startMarketingDate[i] = []; + this.endMarketingDate[i] = []; + } + } + getProductDetails(newType?: string): void { if (this.id != null) { const id = this.id.toString(); @@ -171,6 +191,54 @@ export class ProductFormComponent implements OnInit, AfterViewInit, OnDestroy { this.product.productProvenances = [{ productNames: [], productCodes: [], productDocumentations: [] }]; } + // Load 'Effective Date' value to on the DatePicker Input textbox + if (this.product.effectiveDate) { + this.effectiveDate = new Date(this.product.effectiveDate); + } + + // Load/Assign 'endDate' value on the DatePicker Input textbox + if (this.product.endDate) { + this.endDate = new Date(this.product.endDate); + } + + // Load/Assign 'Start Marketing Date' and 'End Marketing Date' values on the DatePicker Input textbox + if (this.product.productProvenances.length > 0) { + this.product.productProvenances.forEach((elementProv, indexProv) => { + if (elementProv != null) { + + // Loop Companies + elementProv.productCompanies.forEach((elementComp, indexComp) => { + if (elementComp.startMarketingDate) { + if (this.startMarketingDate[indexProv] == null) { + this.startMarketingDate[indexProv] = []; + } + this.startMarketingDate[indexProv][indexComp] = new Date(elementComp.startMarketingDate); + } + + // Load/Assign End Marketing Date in Datepicker Input Textbox + if (elementComp.endMarketingDate) { + if (this.endMarketingDate[indexProv] == null) { + this.endMarketingDate[indexProv] = []; + } + this.endMarketingDate[indexProv][indexComp] = new Date(elementComp.endMarketingDate); + } + }); // loop companies + + // Loop Document IDs + elementProv.productDocumentations.forEach((elementDoc, indexDoc) => { + // Load/Assign 'Effective Time' value on the DatePicker Input textbox + if (elementDoc.effectiveTime) { + if (this.effectiveTime[indexProv] == null) { + this.effectiveTime[indexProv] = []; + } + this.effectiveTime[indexProv][indexDoc] = new Date(elementDoc.effectiveTime); + } + }); // loop document Ids. + + } // provenances object exists + }); // loop provenances + } // if provenances length > 0 + } else { this.message = 'No Product Record found for Id ' + this.id; } @@ -230,12 +298,6 @@ export class ProductFormComponent implements OnInit, AfterViewInit, OnDestroy { this.validationMessages = []; this.validationResult = true; - // Validate Provenance field in Provenance section - this.validateProvenanceField('main-validation'); - - // Validate Effective Time in Documentation IDs - this.validateEffectiveTime('main-validation'); - // Validate Effective Date Date in Product Overview section if (this.product.effectiveDate) { const isValidEffectiveDate = this.validateDate(this.product.effectiveDate); @@ -252,6 +314,15 @@ export class ProductFormComponent implements OnInit, AfterViewInit, OnDestroy { } } + // Validate Provenance field in Provenance section + this.validateProvenanceField('main-validation'); + + // Validate Start Marketing Date and End Marketing Date in Company section + this.validateMarketingDate('main-validation'); + + // Validate Effective Time in Documentation IDs + this.validateEffectiveTime('main-validation'); + // Validate Expiry Date in Lot section if ((this.expiryDateMessage !== null) && (this.expiryDateMessage.length > 0)) { this.setValidationMessage(this.expiryDateMessage); @@ -349,6 +420,41 @@ export class ProductFormComponent implements OnInit, AfterViewInit, OnDestroy { } } + validateMarketingDate(type?: string) { + // Validate Start and End Marketing Date in Provenance Company section + if (this.product != null) { + this.product.productProvenances.forEach((elementProv, indexProv) => { + if (elementProv != null) { + elementProv.productCompanies.forEach((elementComp, indexComp) => { + + // Validate Start Marketing Date + if (elementComp.startMarketingDate) { + const isValid = this.validateDate(elementComp.startMarketingDate); + + if (isValid === false) { + if (type && type === 'main-validation') { + this.setValidationMessage('Start Marketing Date is invalid in Product Provenance ' + (indexProv + 1) + ' in Product Company ' + (indexComp + 1)); + } + } + } + + // Validate End Marketing Date + if (elementComp.endMarketingDate) { + const isValid = this.validateDate(elementComp.endMarketingDate); + + if (isValid === false) { + if (type && type === 'main-validation') { + this.setValidationMessage('End Marketing Date is invalid in Product Provenance ' + (indexProv + 1) + ' in Product Company ' + (indexComp + 1)); + } + } + } + + }); + } + }); + } + } + validateEffectiveTime(type?: string) { // Validate Effective Time in Provenance Documentation IDs section if (this.product != null) { @@ -510,7 +616,7 @@ export class ProductFormComponent implements OnInit, AfterViewInit, OnDestroy { let cleanProduct = this.cleanProduct(); - let data = {jsonData: cleanProduct, jsonFilename: jsonFilename}; + let data = { jsonData: cleanProduct, jsonFilename: jsonFilename }; const dialogRef = this.dialog.open(JsonDialogFdaComponent, { width: '90%', @@ -572,7 +678,6 @@ export class ProductFormComponent implements OnInit, AfterViewInit, OnDestroy { // Display Existing Provenance field Validation this.validateProvenanceField(); - } addNewProductNameInProv(prodProvenanceIndex: number) { @@ -835,33 +940,55 @@ export class ProductFormComponent implements OnInit, AfterViewInit, OnDestroy { } changeEffectiveDate(event: MatDatepickerInputEvent): void { - if (event.value) { - this.product.effectiveDate = moment(event.value).format('MM/DD/YYYY'); - } + const inputElement: HTMLElement = event.targetElement; + const inputValue: string = (inputElement as HTMLInputElement).value; + + this.product.effectiveDate = inputValue; } changeEndDate(event: MatDatepickerInputEvent): void { - if (event.value) { + const inputElement: HTMLElement = event.targetElement; + const inputValue: string = (inputElement as HTMLInputElement).value; + + this.product.endDate = inputValue; + + /*if (event.value) { this.product.endDate = moment(event.value).format('MM/DD/YYYY'); - } + }*/ } changestartMarketingDate(event: MatDatepickerInputEvent, prodProvIndex: number, prodCompanyIndex: number): void { + const inputElement: HTMLElement = event.targetElement; + const inputValue: string = (inputElement as HTMLInputElement).value; + + this.product.productProvenances[prodProvIndex].productCompanies[prodCompanyIndex].startMarketingDate = inputValue; + + /* if (event.value) { this.product.productProvenances[prodProvIndex].productCompanies[prodCompanyIndex].startMarketingDate = moment(event.value).format('MM/DD/YYYY'); - } + } */ } changeEndMarketingDate(event: MatDatepickerInputEvent, prodProvIndex: number, prodCompanyIndex: number): void { - if (event.value) { + const inputElement: HTMLElement = event.targetElement; + const inputValue: string = (inputElement as HTMLInputElement).value; + + this.product.productProvenances[prodProvIndex].productCompanies[prodCompanyIndex].endMarketingDate = inputValue; + + /*if (event.value) { this.product.productProvenances[prodProvIndex].productCompanies[prodCompanyIndex].endMarketingDate = moment(event.value).format('MM/DD/YYYY'); - } + }*/ } changeEffectiveTime(event: MatDatepickerInputEvent, prodProvIndex: number, prodDocIndex: number): void { - if (event.value) { + const inputElement: HTMLElement = event.targetElement; + const inputValue: string = (inputElement as HTMLInputElement).value; + + this.product.productProvenances[prodProvIndex].productDocumentations[prodDocIndex].effectiveTime = inputValue; + + /*if (event.value) { this.product.productProvenances[prodProvIndex].productDocumentations[prodDocIndex].effectiveTime = moment(event.value).format('MM/DD/YYYY'); - } + }*/ } increaseOverlayZindex(): void { diff --git a/src/app/fda/product/product-form/product-lot-form/product-lot-form.component.html b/src/app/fda/product/product-form/product-lot-form/product-lot-form.component.html index 4b5c2ee08..9720fbb0e 100644 --- a/src/app/fda/product/product-form/product-lot-form/product-lot-form.component.html +++ b/src/app/fda/product/product-form/product-lot-form/product-lot-form.component.html @@ -47,14 +47,14 @@ Expiry Date (mm/dd/yyyy) - + Manufacture Date (mm/dd/yyyy) - + @@ -65,13 +65,13 @@ [(ngModel)]="productLot.expiryDate" name="expiryDate" /> {{expiryDateMessage}} - --> {{manufactureDateMessage}} + -->
    diff --git a/src/app/fda/product/product-form/product-lot-form/product-lot-form.component.scss b/src/app/fda/product/product-form/product-lot-form/product-lot-form.component.scss index 9f38a98b1..11b1b59cb 100644 --- a/src/app/fda/product/product-form/product-lot-form/product-lot-form.component.scss +++ b/src/app/fda/product/product-form/product-lot-form/product-lot-form.component.scss @@ -82,7 +82,7 @@ } .col-5:last-child { - margin-right: 0px; + margin-right: 15px; } .col-5-more { @@ -173,6 +173,10 @@ margin-bottom: -12px; } +.marginright5px { + margin-right: 5px; +} + .maringright10px { margin-right: 10px; } diff --git a/src/app/fda/product/product-form/product-lot-form/product-lot-form.component.ts b/src/app/fda/product/product-form/product-lot-form/product-lot-form.component.ts index 7dd8f9e3d..80bf70370 100644 --- a/src/app/fda/product/product-form/product-lot-form/product-lot-form.component.ts +++ b/src/app/fda/product/product-form/product-lot-form/product-lot-form.component.ts @@ -39,6 +39,10 @@ export class ProductLotFormComponent implements OnInit { shapeList: Array = []; scoringList: Array = []; reviewProductMessage: Array = []; + + expiryDate: any[][] = []; + manufactureDate: any[][] = []; + productMessage = ''; username = null; expiryDateMessage = ''; @@ -55,7 +59,18 @@ export class ProductLotFormComponent implements OnInit { ngOnInit() { this.overlayContainer = this.overlayContainerService.getContainerElement(); this.username = this.authService.getUser(); - // this.getVocabularies(); + + this.initializeDateFieldArray(); + + this.getLotDetails(); + } + + initializeDateFieldArray() { + // Assign date field arrays to empty object + for (let i = 0; i < 10; i++) { + this.expiryDate[i] = []; + this.manufactureDate[i] = []; + } } /* @@ -70,6 +85,31 @@ export class ProductLotFormComponent implements OnInit { }); } */ + getLotDetails() { + // If updating record, load date fields in the Datepicker Input Textbox + if (this.productLot != null) { + if (this.productLot.id != null) { + + if (this.productLot.expiryDate) { + if (this.expiryDate[this.prodComponentIndex] == null) { + this.expiryDate[this.prodComponentIndex] = []; + } + // Load/Assign 'Expiry Date' value on the DatePicker Input textbox + this.expiryDate[this.prodComponentIndex][this.prodLotIndex] = new Date(this.productLot.expiryDate); + } + + if (this.productLot.manufactureDate) { + if (this.manufactureDate[this.prodComponentIndex] == null) { + this.manufactureDate[this.prodComponentIndex] = []; + } + // Load/Assign 'Manufacture Date' value on the DatePicker Input textbox + this.manufactureDate[this.prodComponentIndex][this.prodLotIndex] = new Date(this.productLot.manufactureDate); + } + + } + } + } + confirmDeleteProductLot(prodComponentIndex: number, prodLotIndex: number) { const dialogRef = this.dialog.open(ConfirmDialogComponent, { data: { message: 'Are you sure you want to delete Product Lot Details ' + (prodLotIndex + 1) + ' data?' } @@ -98,7 +138,7 @@ export class ProductLotFormComponent implements OnInit { this.expiryDateMessage = ''; const isValid = this.validateDate(this.productLot.expiryDate); if (isValid === false) { - this.expiryDateMessage = 'Expiry Date is invalid'; + this.expiryDateMessage = 'Expiry Date is invalid in Manufacture Item ' + (this.prodComponentIndex + 1) + ' in Lot ' + (this.prodLotIndex + 1); } this.expiryDateMessageOut.emit(this.expiryDateMessage); } @@ -107,7 +147,7 @@ export class ProductLotFormComponent implements OnInit { this.manufactureDateMessage = ''; const isValid = this.validateDate(this.productLot.manufactureDate); if (isValid === false) { - this.manufactureDateMessage = 'Manufacture Date is invalid'; + this.manufactureDateMessage = 'Manufacture Date is invalid in Manufacture Item ' + (this.prodComponentIndex + 1) + ' in Lot ' + (this.prodLotIndex + 1); } this.manufactureDateMessageOut.emit(this.manufactureDateMessage); } @@ -138,27 +178,33 @@ export class ProductLotFormComponent implements OnInit { } changeExpiryDate(event: MatDatepickerInputEvent): void { - const selectedDate: Date | null = event.value; + const inputElement: HTMLElement = event.targetElement; + const inputValue: any = (inputElement as HTMLInputElement).value; - if (selectedDate) { - let dateFormattedStr = selectedDate.getMonth()+1 + '/' + selectedDate.getDate() + '/' + selectedDate.getFullYear(); - let dateObject: Date = new Date(dateFormattedStr); - - this.productLot.expiryDate = dateObject; - } + this.productLot.expiryDate = inputValue; + + this.validateExpiryDate(); } changeManufactureDate(event: MatDatepickerInputEvent): void { + const inputElement: HTMLElement = event.targetElement; + const inputValue: any = (inputElement as HTMLInputElement).value; + + this.productLot.manufactureDate = inputValue; + + this.validateManufactureDate(); + + /* const selectedDate: Date | null = event.value; if (selectedDate) { - let dateFormattedStr = selectedDate.getMonth()+1 + '/' + selectedDate.getDate() + '/' + selectedDate.getFullYear(); + let dateFormattedStr = selectedDate.getMonth() + 1 + '/' + selectedDate.getDate() + '/' + selectedDate.getFullYear(); let dateObject: Date = new Date(dateFormattedStr); this.productLot.manufactureDate = dateObject; - } + } */ } - + increaseOverlayZindex(): void { this.overlayContainer.style.zIndex = '1002'; } From 69d9470cc6e1938d0a21d0e1e1f0e57179b5acf0 Mon Sep 17 00:00:00 2001 From: Newatia Date: Wed, 10 Sep 2025 11:39:11 -0400 Subject: [PATCH 088/408] removed comment code --- ...harmacology-assay-data-import.component.ts | 4 ---- .../product-form/product-form.component.ts | 17 --------------- .../product-lot-form.component.ts | 21 ------------------- 3 files changed, 42 deletions(-) diff --git a/src/app/fda/invitro-pharmacology/invitro-pharmacology-assay-data-import/invitro-pharmacology-assay-data-import.component.ts b/src/app/fda/invitro-pharmacology/invitro-pharmacology-assay-data-import/invitro-pharmacology-assay-data-import.component.ts index 916adbdb0..3d126e244 100644 --- a/src/app/fda/invitro-pharmacology/invitro-pharmacology-assay-data-import/invitro-pharmacology-assay-data-import.component.ts +++ b/src/app/fda/invitro-pharmacology/invitro-pharmacology-assay-data-import/invitro-pharmacology-assay-data-import.component.ts @@ -556,10 +556,6 @@ export class InvitroPharmacologyAssayDataImportComponent implements OnInit { element["targetNameSubstanceKey"] = substanceKey; element["targetNameSubstanceKeyType"] = this.substanceKeyTypeForInvitroPharmacologyConfig; - if (substance.approvalID) { - - } - if ((element["targetNameApprovalId"]) && (element["targetNameApprovalId"] !== substance.approvalID)) { this.setValidationMessage(this.TARGET_NAME + ' Approval ID "' + element["targetNameApprovalId"] + '" in Excel file does not match with Approval ID "' + substance.approvalID + '" for "' + ingredientName + '" in the database. Please fix in the Excel file and then import again', validationMessages, index); } diff --git a/src/app/fda/product/product-form/product-form.component.ts b/src/app/fda/product/product-form/product-form.component.ts index a45de3bec..4e6a43db8 100644 --- a/src/app/fda/product/product-form/product-form.component.ts +++ b/src/app/fda/product/product-form/product-form.component.ts @@ -951,10 +951,6 @@ export class ProductFormComponent implements OnInit, AfterViewInit, OnDestroy { const inputValue: string = (inputElement as HTMLInputElement).value; this.product.endDate = inputValue; - - /*if (event.value) { - this.product.endDate = moment(event.value).format('MM/DD/YYYY'); - }*/ } changestartMarketingDate(event: MatDatepickerInputEvent, prodProvIndex: number, prodCompanyIndex: number): void { @@ -962,11 +958,6 @@ export class ProductFormComponent implements OnInit, AfterViewInit, OnDestroy { const inputValue: string = (inputElement as HTMLInputElement).value; this.product.productProvenances[prodProvIndex].productCompanies[prodCompanyIndex].startMarketingDate = inputValue; - - /* - if (event.value) { - this.product.productProvenances[prodProvIndex].productCompanies[prodCompanyIndex].startMarketingDate = moment(event.value).format('MM/DD/YYYY'); - } */ } changeEndMarketingDate(event: MatDatepickerInputEvent, prodProvIndex: number, prodCompanyIndex: number): void { @@ -974,10 +965,6 @@ export class ProductFormComponent implements OnInit, AfterViewInit, OnDestroy { const inputValue: string = (inputElement as HTMLInputElement).value; this.product.productProvenances[prodProvIndex].productCompanies[prodCompanyIndex].endMarketingDate = inputValue; - - /*if (event.value) { - this.product.productProvenances[prodProvIndex].productCompanies[prodCompanyIndex].endMarketingDate = moment(event.value).format('MM/DD/YYYY'); - }*/ } changeEffectiveTime(event: MatDatepickerInputEvent, prodProvIndex: number, prodDocIndex: number): void { @@ -985,10 +972,6 @@ export class ProductFormComponent implements OnInit, AfterViewInit, OnDestroy { const inputValue: string = (inputElement as HTMLInputElement).value; this.product.productProvenances[prodProvIndex].productDocumentations[prodDocIndex].effectiveTime = inputValue; - - /*if (event.value) { - this.product.productProvenances[prodProvIndex].productDocumentations[prodDocIndex].effectiveTime = moment(event.value).format('MM/DD/YYYY'); - }*/ } increaseOverlayZindex(): void { diff --git a/src/app/fda/product/product-form/product-lot-form/product-lot-form.component.ts b/src/app/fda/product/product-form/product-lot-form/product-lot-form.component.ts index 80bf70370..9daefb07a 100644 --- a/src/app/fda/product/product-form/product-lot-form/product-lot-form.component.ts +++ b/src/app/fda/product/product-form/product-lot-form/product-lot-form.component.ts @@ -73,18 +73,6 @@ export class ProductLotFormComponent implements OnInit { } } - /* - getVocabularies(): void { - this.cvService.getDomainVocabulary('DOSAGE_FORM', 'PROD_CHARACTER_COLOR', 'PROD_CHARACTER_FLAVOR', - 'PROD_CHARACTER_SHAPE', 'PROD_CHARACTER_FRAGMENTS').subscribe(response => { - this.dosageFormList = response['DOSAGE_FORM'].list; - this.colorList = response['PROD_CHARACTER_COLOR'].list; - this.flavorList = response['PROD_CHARACTER_FLAVOR'].list; - this.shapeList = response['PROD_CHARACTER_SHAPE'].list; - this.scoringList = response['PROD_CHARACTER_FRAGMENTS'].list; - }); - } -*/ getLotDetails() { // If updating record, load date fields in the Datepicker Input Textbox if (this.productLot != null) { @@ -194,15 +182,6 @@ export class ProductLotFormComponent implements OnInit { this.validateManufactureDate(); - /* - const selectedDate: Date | null = event.value; - - if (selectedDate) { - let dateFormattedStr = selectedDate.getMonth() + 1 + '/' + selectedDate.getDate() + '/' + selectedDate.getFullYear(); - let dateObject: Date = new Date(dateFormattedStr); - - this.productLot.manufactureDate = dateObject; - } */ } increaseOverlayZindex(): void { From bd2cb945a6495ccc13368eb3ed821fdca0ace7a9 Mon Sep 17 00:00:00 2001 From: Newatia Date: Wed, 10 Sep 2025 15:42:37 -0400 Subject: [PATCH 089/408] updated IVP Import --- ...rmacology-assay-data-import.component.html | 2 +- ...harmacology-assay-data-import.component.ts | 21 +--- ...ology-screening-data-import.component.html | 97 +------------------ ...acology-screening-data-import.component.ts | 16 ++- 4 files changed, 16 insertions(+), 120 deletions(-) diff --git a/src/app/fda/invitro-pharmacology/invitro-pharmacology-assay-data-import/invitro-pharmacology-assay-data-import.component.html b/src/app/fda/invitro-pharmacology/invitro-pharmacology-assay-data-import/invitro-pharmacology-assay-data-import.component.html index 350c63e17..4721c7b82 100644 --- a/src/app/fda/invitro-pharmacology/invitro-pharmacology-assay-data-import/invitro-pharmacology-assay-data-import.component.html +++ b/src/app/fda/invitro-pharmacology/invitro-pharmacology-assay-data-import/invitro-pharmacology-assay-data-import.component.html @@ -55,7 +55,7 @@ -
    +
    diff --git a/src/app/fda/invitro-pharmacology/invitro-pharmacology-assay-data-import/invitro-pharmacology-assay-data-import.component.ts b/src/app/fda/invitro-pharmacology/invitro-pharmacology-assay-data-import/invitro-pharmacology-assay-data-import.component.ts index 3d126e244..989f2136d 100644 --- a/src/app/fda/invitro-pharmacology/invitro-pharmacology-assay-data-import/invitro-pharmacology-assay-data-import.component.ts +++ b/src/app/fda/invitro-pharmacology/invitro-pharmacology-assay-data-import/invitro-pharmacology-assay-data-import.component.ts @@ -119,32 +119,13 @@ export class InvitroPharmacologyAssayDataImportComponent implements OnInit { onFileChange(evt) { // Get Data from Excel File - /* - let workBook = null; - let jsonData = null; - const reader = new FileReader(); - const file = ev.target.files[0]; - reader.onload = (event) => { - const data = reader.result; - workBook = XLSX.read(data, { type: 'binary' }); - jsonData = workBook.SheetNames.reduce((initial, name) => { - const sheet = workBook.Sheets[name]; - initial[name] = XLSX.utils.sheet_to_json(sheet); - return initial; - }, {}); - const dataString = JSON.stringify(jsonData); - document.getElementById('output').innerHTML = dataString.slice(0, 300).concat("..."); - this.setDownload(dataString); - } - reader.readAsBinaryString(file); - */ - const target: DataTransfer = (evt.target); if (target.files.length > 1) { alert('Multiple files are not allowed'); return; } else { + this.importSaveMessageArray = []; this.importValidateMessageArray = []; this.disableImportButton = 'true'; this.isAllRecordValidated = false; diff --git a/src/app/fda/invitro-pharmacology/invitro-pharmacology-screening-data-import/invitro-pharmacology-screening-data-import.component.html b/src/app/fda/invitro-pharmacology/invitro-pharmacology-screening-data-import/invitro-pharmacology-screening-data-import.component.html index 14b7c33cb..032cb7f0f 100644 --- a/src/app/fda/invitro-pharmacology/invitro-pharmacology-screening-data-import/invitro-pharmacology-screening-data-import.component.html +++ b/src/app/fda/invitro-pharmacology/invitro-pharmacology-screening-data-import/invitro-pharmacology-screening-data-import.component.html @@ -23,79 +23,6 @@
    - - - - - - -
    @@ -146,7 +73,7 @@ 1. Reference Information

    Reference Source Type *: - + {{invitroReference.sourceType}} @@ -389,7 +316,6 @@
    NumberAssay found in DatabaseAssay Found In DatabaseAssay Set External Assay Source * External Assay ID *External Assay URL/Document Link Assay ID Test Date (mm/dd/yyyy) Test Agent ConcentrationMeasurements
    {{(index+1)}}{{(indexResult+1)}} @@ -395,6 +508,7 @@
    + Register Assay @@ -405,6 +519,8 @@
    {{result.assaySet}}
    {{result.externalAssaySource}} @@ -412,6 +528,12 @@ {{result.externalAssaySource}} + + + + Required Field +
    @@ -421,6 +543,12 @@ {{result.externalAssayId}} + + + + Required Field + {{result.externalAssayUrl}}
    - @@ -403,26 +329,6 @@ - - - diff --git a/src/app/fda/invitro-pharmacology/invitro-pharmacology-screening-data-import/invitro-pharmacology-screening-data-import.component.ts b/src/app/fda/invitro-pharmacology/invitro-pharmacology-screening-data-import/invitro-pharmacology-screening-data-import.component.ts index 770d97748..7f31dc2ac 100644 --- a/src/app/fda/invitro-pharmacology/invitro-pharmacology-screening-data-import/invitro-pharmacology-screening-data-import.component.ts +++ b/src/app/fda/invitro-pharmacology/invitro-pharmacology-screening-data-import/invitro-pharmacology-screening-data-import.component.ts @@ -92,12 +92,13 @@ export class InvitroPharmacologyScreeningDataImportComponent implements OnInit { ) { } ngOnInit(): void { + this.titleService.setTitle("IVP Import Screening Data"); this.authService.hasAnyRolesAsync('Admin', 'Updater', 'SuperUpdater').pipe(take(1)).subscribe(response => { this.isAdmin = response; }); - - this.titleService.setTitle("IVP Import Screening Data"); + + this.initializeRequiredFieldArray(); } ngOnDestroy(): void { @@ -115,7 +116,9 @@ export class InvitroPharmacologyScreeningDataImportComponent implements OnInit { else { // Empty the list this.importDataList.length = 0; - this.requiredFieldMissingArray = [{}]; + //requiredFieldMissingArray = [{}]; + + this.initializeRequiredFieldArray(); // Assign FileReader const reader: FileReader = new FileReader(); @@ -160,6 +163,13 @@ export class InvitroPharmacologyScreeningDataImportComponent implements OnInit { } + initializeRequiredFieldArray() { + this.requiredFieldMissingArray = [{}]; + + this.requiredFieldMissingArray[0].sourceType = false; + this.requiredFieldMissingArray[0].controlExternalAssaySource = false; + + } getInvitroReference(workbook: XLSX.WorkBook) { // Read the Second Excel Spreadsheet, the worksheet index starts with 0. // Read Sheet "1. Reference and Laboratory" From af67f9ecdf0ae4447965b884b1156eaf2d1e3d0c Mon Sep 17 00:00:00 2001 From: Jaroslav Iha Date: Thu, 11 Sep 2025 12:37:17 +0200 Subject: [PATCH 090/408] svg save test --- .../substance-ssg4m/substance-ssg4m-form.component.ts | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/app/core/substance-ssg4m/substance-ssg4m-form.component.ts b/src/app/core/substance-ssg4m/substance-ssg4m-form.component.ts index 95a0cf00c..e96b3bf38 100644 --- a/src/app/core/substance-ssg4m/substance-ssg4m-form.component.ts +++ b/src/app/core/substance-ssg4m/substance-ssg4m-form.component.ts @@ -1009,7 +1009,16 @@ export class SubstanceSsg4ManufactureFormComponent implements OnInit, AfterViewI // Save SVG as Clob this.ssg4mSyntheticPathway.sbmsnImage = document.querySelector("#scheme-viz-view").innerHTML; - const dataUrl = await toSvg(document.querySelector('app-ssg4m-scheme-view') as HTMLElement); + const options = { + fetchRequestInit: { + headers: new Headers(), + mode: 'cors' as RequestMode, // Important for fetching from other domains like Google Fonts + cache: 'default' as RequestCache + }, + // We can explicitly tell it to include all fonts. + fontEmbedCSS: '@font-face' + } + const dataUrl = await toSvg(document.querySelector('app-ssg4m-scheme-view') as HTMLElement, options); const commaIndex = dataUrl.indexOf(','); const encodedSvg = dataUrl.slice(commaIndex + 1); const downloadLink = document.createElement('a'); From a60d8e93c68e075a1f1ad3e49f7f6046754ba0b9 Mon Sep 17 00:00:00 2001 From: Newatia Date: Thu, 11 Sep 2025 15:20:07 -0400 Subject: [PATCH 091/408] updated Datepicker fields --- src/app/fda/product/model/product.model.ts | 5 + .../product-form/product-form.component.html | 4 +- .../product-form/product-form.component.ts | 136 +++++++++--------- .../product-lot-form.component.html | 18 +-- .../product-lot-form.component.ts | 27 +--- 5 files changed, 83 insertions(+), 107 deletions(-) diff --git a/src/app/fda/product/model/product.model.ts b/src/app/fda/product/model/product.model.ts index 959f42448..33aa5767b 100644 --- a/src/app/fda/product/model/product.model.ts +++ b/src/app/fda/product/model/product.model.ts @@ -126,6 +126,8 @@ export interface ProductCompany { modifiedBy?: string; modifyDate?: number; productCompanyCodes?: Array; + _startMarketingDate?: Date; + _endMarketingDate?: Date; } export interface ProductCompanyCode { @@ -163,6 +165,7 @@ export interface ProductDocumentation { createDate?: number; modifiedBy?: string; modifyDate?: number; + _effectiveTime?: Date; } export interface ProductIndication { @@ -218,6 +221,8 @@ export interface ProductLot { modifiedBy?: string; modifyDate?: number; productIngredients?: Array; + _expiryDate?: Date; + _manufactureDate?: Date; } export interface ProductIngredient { diff --git a/src/app/fda/product/product-form/product-form.component.html b/src/app/fda/product/product-form/product-form.component.html index cea27dd91..76cf937ed 100644 --- a/src/app/fda/product/product-form/product-form.component.html +++ b/src/app/fda/product/product-form/product-form.component.html @@ -577,14 +577,14 @@ Start Marketing Date (mm/dd/yyyy) - + End Marketing Date (mm/dd/yyyy) - + diff --git a/src/app/fda/product/product-form/product-form.component.ts b/src/app/fda/product/product-form/product-form.component.ts index 4e6a43db8..c3ff72949 100644 --- a/src/app/fda/product/product-form/product-form.component.ts +++ b/src/app/fda/product/product-form/product-form.component.ts @@ -35,10 +35,7 @@ export class ProductFormComponent implements OnInit, AfterViewInit, OnDestroy { /* Array data type */ private subscriptions: Array = []; - startMarketingDate: any[][] = []; - endMarketingDate: any[][] = []; - effectiveTime: any[][] = []; - + validationMessages: Array = []; provenanceFieldMessage: Array = []; effectiveTimeMessage: any[][] = []; @@ -95,8 +92,6 @@ export class ProductFormComponent implements OnInit, AfterViewInit, OnDestroy { this.loadingService.setLoading(true); this.overlayContainer = this.overlayContainerService.getContainerElement(); this.username = this.authService.getUser(); - - this.initializeDateFieldArray(); const routeSubscription = this.activatedRoute .params @@ -133,6 +128,8 @@ export class ProductFormComponent implements OnInit, AfterViewInit, OnDestroy { if (this.product.productProvenances == null) { this.product.productProvenances = []; } + this.loadDateFields(); + this.loadingService.setLoading(false); this.isLoading = false; } @@ -165,15 +162,6 @@ export class ProductFormComponent implements OnInit, AfterViewInit, OnDestroy { expanded = !expanded; } - initializeDateFieldArray() { - // Assign date field arrays to empty object - for (let i = 0; i < 10; i++) { - this.effectiveTime[i] = []; - this.startMarketingDate[i] = []; - this.endMarketingDate[i] = []; - } - } - getProductDetails(newType?: string): void { if (this.id != null) { const id = this.id.toString(); @@ -191,53 +179,7 @@ export class ProductFormComponent implements OnInit, AfterViewInit, OnDestroy { this.product.productProvenances = [{ productNames: [], productCodes: [], productDocumentations: [] }]; } - // Load 'Effective Date' value to on the DatePicker Input textbox - if (this.product.effectiveDate) { - this.effectiveDate = new Date(this.product.effectiveDate); - } - - // Load/Assign 'endDate' value on the DatePicker Input textbox - if (this.product.endDate) { - this.endDate = new Date(this.product.endDate); - } - - // Load/Assign 'Start Marketing Date' and 'End Marketing Date' values on the DatePicker Input textbox - if (this.product.productProvenances.length > 0) { - this.product.productProvenances.forEach((elementProv, indexProv) => { - if (elementProv != null) { - - // Loop Companies - elementProv.productCompanies.forEach((elementComp, indexComp) => { - if (elementComp.startMarketingDate) { - if (this.startMarketingDate[indexProv] == null) { - this.startMarketingDate[indexProv] = []; - } - this.startMarketingDate[indexProv][indexComp] = new Date(elementComp.startMarketingDate); - } - - // Load/Assign End Marketing Date in Datepicker Input Textbox - if (elementComp.endMarketingDate) { - if (this.endMarketingDate[indexProv] == null) { - this.endMarketingDate[indexProv] = []; - } - this.endMarketingDate[indexProv][indexComp] = new Date(elementComp.endMarketingDate); - } - }); // loop companies - - // Loop Document IDs - elementProv.productDocumentations.forEach((elementDoc, indexDoc) => { - // Load/Assign 'Effective Time' value on the DatePicker Input textbox - if (elementDoc.effectiveTime) { - if (this.effectiveTime[indexProv] == null) { - this.effectiveTime[indexProv] = []; - } - this.effectiveTime[indexProv][indexDoc] = new Date(elementDoc.effectiveTime); - } - }); // loop document Ids. - - } // provenances object exists - }); // loop provenances - } // if provenances length > 0 + this.loadDateFields(); } else { this.message = 'No Product Record found for Id ' + this.id; @@ -254,6 +196,47 @@ export class ProductFormComponent implements OnInit, AfterViewInit, OnDestroy { } } + loadDateFields() { + // Load 'Effective Date' value to on the DatePicker Input textbox + if (this.product.effectiveDate) { + this.effectiveDate = new Date(this.product.effectiveDate); + } + + // Load/Assign 'endDate' value on the DatePicker Input textbox + if (this.product.endDate) { + this.endDate = new Date(this.product.endDate); + } + + // Load/Assign 'Start Marketing Date' and 'End Marketing Date' values on the DatePicker Input textbox + if (this.product.productProvenances.length > 0) { + this.product.productProvenances.forEach((elementProv, indexProv) => { + if (elementProv != null) { + + // Loop Companies + elementProv.productCompanies.forEach((elementComp, indexComp) => { + if (elementComp.startMarketingDate) { + elementComp._startMarketingDate = new Date(elementComp.startMarketingDate); + } + + // Load/Assign End Marketing Date in Datepicker Input Textbox + if (elementComp.endMarketingDate) { + elementComp._endMarketingDate = new Date(elementComp.endMarketingDate); + } + }); // loop companies + + // Loop Document IDs + elementProv.productDocumentations.forEach((elementDoc, indexDoc) => { + // Load/Assign 'Effective Time' value on the DatePicker Input textbox + if (elementDoc.effectiveTime) { + elementDoc._effectiveTime = new Date(elementDoc.effectiveTime); + } + }); // loop document Ids. + + } // provenances object exists + }); // loop provenances + } // if provenances length > 0 + } + validate(validationType?: string): void { this.isLoading = true; this.serverError = false; @@ -586,12 +569,34 @@ export class ProductFormComponent implements OnInit, AfterViewInit, OnDestroy { cleanProduct(): Product { let productStr = JSON.stringify(this.product); let productCopy: Product = JSON.parse(productStr); + + productCopy.productProvenances.forEach((elementProv, indexProv) => { + if (elementProv != null) { + // Loop Companies + elementProv.productCompanies.forEach((elementComp, indexComp) => { + delete elementComp._startMarketingDate; + delete elementComp._endMarketingDate; + }); + + // Loop Documentations IDs + elementProv.productDocumentations.forEach((elementDoc, indexDoc) => { + delete elementDoc._effectiveTime; + }); + } + }); + productCopy.productManufactureItems.forEach(elementComp => { if (elementComp != null) { elementComp.productLots.forEach(elementLot => { if (elementLot != null) { + + delete elementLot._expiryDate; + delete elementLot._manufactureDate; + + // Loop Ingredients elementLot.productIngredients.forEach(elementIngred => { if (elementIngred != null) { + // remove property for Ingredient Name Validation. Do not need in the form JSON if (elementIngred.$$ingredientNameValidation || elementIngred.$$ingredientNameValidation === "") { delete elementIngred.$$ingredientNameValidation; @@ -602,6 +607,7 @@ export class ProductFormComponent implements OnInit, AfterViewInit, OnDestroy { } } // if ingred is not null }); // ingred loop + } // if lot is not null }); // lot loop } // if comp is not null @@ -631,10 +637,8 @@ export class ProductFormComponent implements OnInit, AfterViewInit, OnDestroy { } saveJSON(): void { - // apply the same cleaning to remove deleted objects and return what will be sent to the server on validation / submission - this.cleanProduct(); - let json = this.product; - // this.json = this.cleanObject(substanceCopy); + let json = this.cleanProduct(); + const uri = this.sanitizer.bypassSecurityTrustUrl('data:text/json;charset=UTF-8,' + encodeURIComponent(JSON.stringify(json))); this.downloadJsonHref = uri; diff --git a/src/app/fda/product/product-form/product-lot-form/product-lot-form.component.html b/src/app/fda/product/product-form/product-lot-form/product-lot-form.component.html index 9720fbb0e..e52ab013d 100644 --- a/src/app/fda/product/product-form/product-lot-form/product-lot-form.component.html +++ b/src/app/fda/product/product-form/product-lot-form/product-lot-form.component.html @@ -47,31 +47,17 @@ Expiry Date (mm/dd/yyyy) - + Manufacture Date (mm/dd/yyyy) - + - - diff --git a/src/app/fda/product/product-form/product-lot-form/product-lot-form.component.ts b/src/app/fda/product/product-form/product-lot-form/product-lot-form.component.ts index 9daefb07a..ad2f6f5fd 100644 --- a/src/app/fda/product/product-form/product-lot-form/product-lot-form.component.ts +++ b/src/app/fda/product/product-form/product-lot-form/product-lot-form.component.ts @@ -40,9 +40,6 @@ export class ProductLotFormComponent implements OnInit { scoringList: Array = []; reviewProductMessage: Array = []; - expiryDate: any[][] = []; - manufactureDate: any[][] = []; - productMessage = ''; username = null; expiryDateMessage = ''; @@ -60,40 +57,24 @@ export class ProductLotFormComponent implements OnInit { this.overlayContainer = this.overlayContainerService.getContainerElement(); this.username = this.authService.getUser(); - this.initializeDateFieldArray(); - this.getLotDetails(); } - initializeDateFieldArray() { - // Assign date field arrays to empty object - for (let i = 0; i < 10; i++) { - this.expiryDate[i] = []; - this.manufactureDate[i] = []; - } - } - getLotDetails() { // If updating record, load date fields in the Datepicker Input Textbox if (this.productLot != null) { if (this.productLot.id != null) { - + if (this.productLot.expiryDate) { - if (this.expiryDate[this.prodComponentIndex] == null) { - this.expiryDate[this.prodComponentIndex] = []; - } // Load/Assign 'Expiry Date' value on the DatePicker Input textbox - this.expiryDate[this.prodComponentIndex][this.prodLotIndex] = new Date(this.productLot.expiryDate); + this.productLot._expiryDate = new Date(this.productLot.expiryDate); } if (this.productLot.manufactureDate) { - if (this.manufactureDate[this.prodComponentIndex] == null) { - this.manufactureDate[this.prodComponentIndex] = []; - } // Load/Assign 'Manufacture Date' value on the DatePicker Input textbox - this.manufactureDate[this.prodComponentIndex][this.prodLotIndex] = new Date(this.productLot.manufactureDate); + this.productLot._manufactureDate = new Date(this.productLot.manufactureDate); } - + } } } From 1d58c5dc30a2628a1625e9c08a36f4c6001f58d6 Mon Sep 17 00:00:00 2001 From: Newatia Date: Fri, 12 Sep 2025 10:46:49 -0400 Subject: [PATCH 092/408] updated date picker --- src/app/fda/product/product-form/product-form.component.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/fda/product/product-form/product-form.component.html b/src/app/fda/product/product-form/product-form.component.html index 76cf937ed..0f56f5621 100644 --- a/src/app/fda/product/product-form/product-form.component.html +++ b/src/app/fda/product/product-form/product-form.component.html @@ -775,7 +775,7 @@ Effective Time (mm/dd/yyyy) - + From f6d82de85d1e2903d31230af1c9685acebd11909 Mon Sep 17 00:00:00 2001 From: Jaroslav Iha Date: Mon, 15 Sep 2025 13:43:21 +0200 Subject: [PATCH 093/408] svg save test --- .../substance-ssg4m-form.component.ts | 51 ++++++++++++++++--- 1 file changed, 45 insertions(+), 6 deletions(-) diff --git a/src/app/core/substance-ssg4m/substance-ssg4m-form.component.ts b/src/app/core/substance-ssg4m/substance-ssg4m-form.component.ts index e96b3bf38..965da64a5 100644 --- a/src/app/core/substance-ssg4m/substance-ssg4m-form.component.ts +++ b/src/app/core/substance-ssg4m/substance-ssg4m-form.component.ts @@ -982,6 +982,23 @@ export class SubstanceSsg4ManufactureFormComponent implements OnInit, AfterViewI }); } + getPageStyles(): string { + let css = ''; + // Gather all style rules from the document + for (const sheet of Array.from(document.styleSheets)) { + try { + if (sheet.cssRules) { + css += Array.from(sheet.cssRules) + .map(rule => rule.cssText) + .join('\n'); + } + } catch (e) { + console.warn('Cannot read styles from cross-origin stylesheet', e); + } + } + return ``; + } + submit(): void { this.isLoading = true; this.loadingService.setLoading(true); @@ -1009,16 +1026,37 @@ export class SubstanceSsg4ManufactureFormComponent implements OnInit, AfterViewI // Save SVG as Clob this.ssg4mSyntheticPathway.sbmsnImage = document.querySelector("#scheme-viz-view").innerHTML; + // const options = { + // fetchRequestInit: { + // headers: new Headers(), + // mode: 'cors' as RequestMode, // Important for fetching from other domains like Google Fonts + // cache: 'default' as RequestCache + // }, + // // We can explicitly tell it to include all fonts. + // fontEmbedCSS: '@font-face' + // } + const elementToConvert = document.querySelector('app-ssg4m-scheme-view') as HTMLElement; + const clone = elementToConvert.cloneNode(true) as HTMLElement; + const container = document.createElement('div'); + container.style.position = 'absolute'; + container.style.left = '-9999px'; + const styles = this.getPageStyles(); + container.innerHTML = styles; + container.appendChild(clone); + document.body.appendChild(container); + const options = { + width: elementToConvert.offsetWidth, + height: elementToConvert.offsetHeight, + // You may still need the fetch options for external images/fonts fetchRequestInit: { headers: new Headers(), - mode: 'cors' as RequestMode, // Important for fetching from other domains like Google Fonts + mode: 'cors' as RequestMode, cache: 'default' as RequestCache - }, - // We can explicitly tell it to include all fonts. - fontEmbedCSS: '@font-face' - } - const dataUrl = await toSvg(document.querySelector('app-ssg4m-scheme-view') as HTMLElement, options); + } + }; + + const dataUrl = await toSvg(clone, options); const commaIndex = dataUrl.indexOf(','); const encodedSvg = dataUrl.slice(commaIndex + 1); const downloadLink = document.createElement('a'); @@ -1037,6 +1075,7 @@ export class SubstanceSsg4ManufactureFormComponent implements OnInit, AfterViewI // 6. Remove the link from the document document.body.removeChild(downloadLink); + document.body.removeChild(container); this.ssg4mSyntheticPathway.stepViewImage = decodeURIComponent(encodedSvg); From 381ef791268e6d41bc05acd81234a68d493dd229 Mon Sep 17 00:00:00 2001 From: Jaroslav Iha Date: Mon, 15 Sep 2025 18:12:14 +0200 Subject: [PATCH 094/408] svg save test --- .../core/substance-ssg4m/substance-ssg4m-form.component.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/app/core/substance-ssg4m/substance-ssg4m-form.component.ts b/src/app/core/substance-ssg4m/substance-ssg4m-form.component.ts index 965da64a5..67ead515b 100644 --- a/src/app/core/substance-ssg4m/substance-ssg4m-form.component.ts +++ b/src/app/core/substance-ssg4m/substance-ssg4m-form.component.ts @@ -999,6 +999,10 @@ export class SubstanceSsg4ManufactureFormComponent implements OnInit, AfterViewI return ``; } + delay(ms: number): Promise { + return new Promise(resolve => setTimeout(resolve, ms)); + } + submit(): void { this.isLoading = true; this.loadingService.setLoading(true); @@ -1044,6 +1048,7 @@ export class SubstanceSsg4ManufactureFormComponent implements OnInit, AfterViewI container.innerHTML = styles; container.appendChild(clone); document.body.appendChild(container); + await this.delay(5000) const options = { width: elementToConvert.offsetWidth, From 247fdc02d9b376e7b1ddd9333ddeb4da8531e0e1 Mon Sep 17 00:00:00 2001 From: Jaroslav Iha Date: Tue, 16 Sep 2025 14:52:53 +0200 Subject: [PATCH 095/408] svg save test --- .../substance-ssg4m-form.component.ts | 84 +++++++------------ 1 file changed, 32 insertions(+), 52 deletions(-) diff --git a/src/app/core/substance-ssg4m/substance-ssg4m-form.component.ts b/src/app/core/substance-ssg4m/substance-ssg4m-form.component.ts index 67ead515b..45f8411a0 100644 --- a/src/app/core/substance-ssg4m/substance-ssg4m-form.component.ts +++ b/src/app/core/substance-ssg4m/substance-ssg4m-form.component.ts @@ -1003,6 +1003,37 @@ export class SubstanceSsg4ManufactureFormComponent implements OnInit, AfterViewI return new Promise(resolve => setTimeout(resolve, ms)); } + async exportStepView(document: Document): Promise { + const elementToConvert = document.querySelector('app-ssg4m-scheme-view') as HTMLElement; + const clone = elementToConvert.cloneNode(true) as HTMLElement; + + const container = document.createElement('div'); + container.style.position = 'absolute'; + container.style.left = '-9999px'; + const styles = this.getPageStyles(); + container.innerHTML = styles; + container.appendChild(clone); + document.body.appendChild(container); + + await this.delay(5000) + + const options = { + width: elementToConvert.offsetWidth, + height: elementToConvert.offsetHeight, + // You may still need the fetch options for external images/fonts + fetchRequestInit: { + headers: new Headers(), + mode: 'cors' as RequestMode, + cache: 'default' as RequestCache + } + }; + + const dataUrl = await toSvg(clone, options); + const commaIndex = dataUrl.indexOf(','); + document.body.removeChild(container); + return dataUrl.slice(commaIndex + 1); + } + submit(): void { this.isLoading = true; this.loadingService.setLoading(true); @@ -1029,59 +1060,8 @@ export class SubstanceSsg4ManufactureFormComponent implements OnInit, AfterViewI // Save SVG as Clob this.ssg4mSyntheticPathway.sbmsnImage = document.querySelector("#scheme-viz-view").innerHTML; - - // const options = { - // fetchRequestInit: { - // headers: new Headers(), - // mode: 'cors' as RequestMode, // Important for fetching from other domains like Google Fonts - // cache: 'default' as RequestCache - // }, - // // We can explicitly tell it to include all fonts. - // fontEmbedCSS: '@font-face' - // } - const elementToConvert = document.querySelector('app-ssg4m-scheme-view') as HTMLElement; - const clone = elementToConvert.cloneNode(true) as HTMLElement; - const container = document.createElement('div'); - container.style.position = 'absolute'; - container.style.left = '-9999px'; - const styles = this.getPageStyles(); - container.innerHTML = styles; - container.appendChild(clone); - document.body.appendChild(container); - await this.delay(5000) - - const options = { - width: elementToConvert.offsetWidth, - height: elementToConvert.offsetHeight, - // You may still need the fetch options for external images/fonts - fetchRequestInit: { - headers: new Headers(), - mode: 'cors' as RequestMode, - cache: 'default' as RequestCache - } - }; - - const dataUrl = await toSvg(clone, options); - const commaIndex = dataUrl.indexOf(','); - const encodedSvg = dataUrl.slice(commaIndex + 1); - const downloadLink = document.createElement('a'); - - // 2. Set the href attribute to the data URL - downloadLink.href = dataUrl; - - // 3. Set the download attribute to the desired file name - downloadLink.download = 'img.svg'; - - // 4. Append the link to the document. This is required for Firefox. - document.body.appendChild(downloadLink); - - // 5. Programmatically click the link to initiate the download - downloadLink.click(); - - // 6. Remove the link from the document - document.body.removeChild(downloadLink); - document.body.removeChild(container); + const encodedSvg = await this.exportStepView(document) this.ssg4mSyntheticPathway.stepViewImage = decodeURIComponent(encodedSvg); // After submitting Save button, the UI waits for 5 seconds to see if it gets a response. From 8c548dd8d7c7dedd7c7e4aac05c3e18e1dc8cf7e Mon Sep 17 00:00:00 2001 From: Jaroslav Iha Date: Wed, 17 Sep 2025 11:49:02 +0200 Subject: [PATCH 096/408] svg save test --- .../substance-ssg4m-form.component.ts | 35 ++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) diff --git a/src/app/core/substance-ssg4m/substance-ssg4m-form.component.ts b/src/app/core/substance-ssg4m/substance-ssg4m-form.component.ts index 45f8411a0..371590997 100644 --- a/src/app/core/substance-ssg4m/substance-ssg4m-form.component.ts +++ b/src/app/core/substance-ssg4m/substance-ssg4m-form.component.ts @@ -1003,7 +1003,37 @@ export class SubstanceSsg4ManufactureFormComponent implements OnInit, AfterViewI return new Promise(resolve => setTimeout(resolve, ms)); } + // waitForElement(selector: string, timeout = 2000): Promise { + // return new Promise((resolve, reject) => { + // const interval = setInterval(() => { + // const element = document.querySelector(selector) as HTMLElement; + // if (element) { + // clearInterval(interval); + // resolve(element); + // } + // }, 100); + // setTimeout(() => { + // clearInterval(interval); + // reject(new Error(`Element "${selector}" not found within ${timeout}ms.`)); + // }, timeout); + // }); + // } + async exportStepView(document: Document): Promise { + const tabProcesses = document.querySelector("#mat-expansion-panel-header-2") as HTMLElement; + if (tabProcesses.getAttribute('aria-expanded') !== 'true') { + console.log('Tab Processes not selected. Clicking it...'); + tabProcesses.click(); + await this.delay(200) + } + + const tabStepView = document.querySelector("#mat-tab-label-0-1") as HTMLElement; + if (tabStepView.getAttribute('aria-selected') !== 'true') { + console.log('Tab Step View not selected. Clicking it...'); + tabStepView.click(); + await this.delay(200) + } + const elementToConvert = document.querySelector('app-ssg4m-scheme-view') as HTMLElement; const clone = elementToConvert.cloneNode(true) as HTMLElement; @@ -1017,10 +1047,13 @@ export class SubstanceSsg4ManufactureFormComponent implements OnInit, AfterViewI await this.delay(5000) + function filter (node) { + return (node.tagName !== 'button'); + } const options = { + filter: filter, width: elementToConvert.offsetWidth, height: elementToConvert.offsetHeight, - // You may still need the fetch options for external images/fonts fetchRequestInit: { headers: new Headers(), mode: 'cors' as RequestMode, From 0dd9cf4f24d1f74932e23dcf95330ba9a800dcc6 Mon Sep 17 00:00:00 2001 From: Jaroslav Iha Date: Wed, 17 Sep 2025 14:15:11 +0200 Subject: [PATCH 097/408] svg save test --- .../substance-ssg4m/substance-ssg4m-form.component.ts | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/app/core/substance-ssg4m/substance-ssg4m-form.component.ts b/src/app/core/substance-ssg4m/substance-ssg4m-form.component.ts index 371590997..636aa245e 100644 --- a/src/app/core/substance-ssg4m/substance-ssg4m-form.component.ts +++ b/src/app/core/substance-ssg4m/substance-ssg4m-form.component.ts @@ -806,7 +806,7 @@ export class SubstanceSsg4ManufactureFormComponent implements OnInit, AfterViewI }, 5000); } - validate(validationType?: string): void { + async validate(validationType?: string): Promise { if (validationType && validationType === 'approval') { this.approving = true; } else { @@ -826,7 +826,7 @@ export class SubstanceSsg4ManufactureFormComponent implements OnInit, AfterViewI this.isLoading = false; // If there is no validation error, submit/save the records without displaying the warning/validation message. if (this.validationMessages.length === 0 && true === true) { - this.submit(); + await this.submit(); } /* if (this.validationMessages.length === 0 && true === true) { @@ -1019,7 +1019,7 @@ export class SubstanceSsg4ManufactureFormComponent implements OnInit, AfterViewI // }); // } - async exportStepView(document: Document): Promise { + async expandStepView(): Promise { const tabProcesses = document.querySelector("#mat-expansion-panel-header-2") as HTMLElement; if (tabProcesses.getAttribute('aria-expanded') !== 'true') { console.log('Tab Processes not selected. Clicking it...'); @@ -1033,7 +1033,9 @@ export class SubstanceSsg4ManufactureFormComponent implements OnInit, AfterViewI tabStepView.click(); await this.delay(200) } + } + async exportStepView(document: Document): Promise { const elementToConvert = document.querySelector('app-ssg4m-scheme-view') as HTMLElement; const clone = elementToConvert.cloneNode(true) as HTMLElement; @@ -1067,7 +1069,8 @@ export class SubstanceSsg4ManufactureFormComponent implements OnInit, AfterViewI return dataUrl.slice(commaIndex + 1); } - submit(): void { + async submit(): Promise { + await this.expandStepView() this.isLoading = true; this.loadingService.setLoading(true); this.approving = false; From 50aec927629783c1aacd33058c3c6fea387f4f31 Mon Sep 17 00:00:00 2001 From: Jaroslav Iha Date: Wed, 17 Sep 2025 14:59:37 +0200 Subject: [PATCH 098/408] svg save test --- .../substance-ssg4m-form.component.ts | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/src/app/core/substance-ssg4m/substance-ssg4m-form.component.ts b/src/app/core/substance-ssg4m/substance-ssg4m-form.component.ts index 636aa245e..6a31b022b 100644 --- a/src/app/core/substance-ssg4m/substance-ssg4m-form.component.ts +++ b/src/app/core/substance-ssg4m/substance-ssg4m-form.component.ts @@ -806,7 +806,7 @@ export class SubstanceSsg4ManufactureFormComponent implements OnInit, AfterViewI }, 5000); } - async validate(validationType?: string): Promise { + validate(validationType?: string): void { if (validationType && validationType === 'approval') { this.approving = true; } else { @@ -826,7 +826,7 @@ export class SubstanceSsg4ManufactureFormComponent implements OnInit, AfterViewI this.isLoading = false; // If there is no validation error, submit/save the records without displaying the warning/validation message. if (this.validationMessages.length === 0 && true === true) { - await this.submit(); + this.submit(); } /* if (this.validationMessages.length === 0 && true === true) { @@ -1019,7 +1019,7 @@ export class SubstanceSsg4ManufactureFormComponent implements OnInit, AfterViewI // }); // } - async expandStepView(): Promise { + async exportStepView(document: Document): Promise { const tabProcesses = document.querySelector("#mat-expansion-panel-header-2") as HTMLElement; if (tabProcesses.getAttribute('aria-expanded') !== 'true') { console.log('Tab Processes not selected. Clicking it...'); @@ -1033,9 +1033,7 @@ export class SubstanceSsg4ManufactureFormComponent implements OnInit, AfterViewI tabStepView.click(); await this.delay(200) } - } - async exportStepView(document: Document): Promise { const elementToConvert = document.querySelector('app-ssg4m-scheme-view') as HTMLElement; const clone = elementToConvert.cloneNode(true) as HTMLElement; @@ -1069,10 +1067,7 @@ export class SubstanceSsg4ManufactureFormComponent implements OnInit, AfterViewI return dataUrl.slice(commaIndex + 1); } - async submit(): Promise { - await this.expandStepView() - this.isLoading = true; - this.loadingService.setLoading(true); + submit(): void { this.approving = false; this.json = this.substanceFormService.cleanSubstance(); @@ -1089,6 +1084,9 @@ export class SubstanceSsg4ManufactureFormComponent implements OnInit, AfterViewI if (this.ssg4mSyntheticPathway == null) { this.ssg4mSyntheticPathway = {}; } + const encodedSvg = await this.exportStepView(document); + this.isLoading = true; + this.loadingService.setLoading(true); // Existing Record // get the JSON from the SSG4m Form and store as a Clob into the database @@ -1097,7 +1095,6 @@ export class SubstanceSsg4ManufactureFormComponent implements OnInit, AfterViewI // Save SVG as Clob this.ssg4mSyntheticPathway.sbmsnImage = document.querySelector("#scheme-viz-view").innerHTML; - const encodedSvg = await this.exportStepView(document) this.ssg4mSyntheticPathway.stepViewImage = decodeURIComponent(encodedSvg); // After submitting Save button, the UI waits for 5 seconds to see if it gets a response. From 4d97ce9817d9ca19b8e6ef536ac3d5902212d94d Mon Sep 17 00:00:00 2001 From: Jaroslav Iha Date: Wed, 17 Sep 2025 15:01:45 +0200 Subject: [PATCH 099/408] svg save test --- .../core/substance-ssg4m/substance-ssg4m-form.component.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/app/core/substance-ssg4m/substance-ssg4m-form.component.ts b/src/app/core/substance-ssg4m/substance-ssg4m-form.component.ts index 6a31b022b..8c453d91c 100644 --- a/src/app/core/substance-ssg4m/substance-ssg4m-form.component.ts +++ b/src/app/core/substance-ssg4m/substance-ssg4m-form.component.ts @@ -1033,6 +1033,8 @@ export class SubstanceSsg4ManufactureFormComponent implements OnInit, AfterViewI tabStepView.click(); await this.delay(200) } + this.isLoading = true; + this.loadingService.setLoading(true); const elementToConvert = document.querySelector('app-ssg4m-scheme-view') as HTMLElement; const clone = elementToConvert.cloneNode(true) as HTMLElement; @@ -1085,8 +1087,6 @@ export class SubstanceSsg4ManufactureFormComponent implements OnInit, AfterViewI this.ssg4mSyntheticPathway = {}; } const encodedSvg = await this.exportStepView(document); - this.isLoading = true; - this.loadingService.setLoading(true); // Existing Record // get the JSON from the SSG4m Form and store as a Clob into the database From 7fac01b443d5c395a2618c99de668f27b7ec736e Mon Sep 17 00:00:00 2001 From: Jaroslav Iha Date: Wed, 17 Sep 2025 16:21:57 +0200 Subject: [PATCH 100/408] svg save test --- .../substance-ssg4m-form.component.ts | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/src/app/core/substance-ssg4m/substance-ssg4m-form.component.ts b/src/app/core/substance-ssg4m/substance-ssg4m-form.component.ts index 8c453d91c..8271d9225 100644 --- a/src/app/core/substance-ssg4m/substance-ssg4m-form.component.ts +++ b/src/app/core/substance-ssg4m/substance-ssg4m-form.component.ts @@ -806,7 +806,7 @@ export class SubstanceSsg4ManufactureFormComponent implements OnInit, AfterViewI }, 5000); } - validate(validationType?: string): void { + async validate(validationType?: string): Promise { if (validationType && validationType === 'approval') { this.approving = true; } else { @@ -826,7 +826,7 @@ export class SubstanceSsg4ManufactureFormComponent implements OnInit, AfterViewI this.isLoading = false; // If there is no validation error, submit/save the records without displaying the warning/validation message. if (this.validationMessages.length === 0 && true === true) { - this.submit(); + await this.submit(); } /* if (this.validationMessages.length === 0 && true === true) { @@ -1019,8 +1019,8 @@ export class SubstanceSsg4ManufactureFormComponent implements OnInit, AfterViewI // }); // } - async exportStepView(document: Document): Promise { - const tabProcesses = document.querySelector("#mat-expansion-panel-header-2") as HTMLElement; + async expandStepView(): Promise { + const tabProcesses = document.querySelector("#substance-form-ssg4m-process") as HTMLElement; if (tabProcesses.getAttribute('aria-expanded') !== 'true') { console.log('Tab Processes not selected. Clicking it...'); tabProcesses.click(); @@ -1033,9 +1033,9 @@ export class SubstanceSsg4ManufactureFormComponent implements OnInit, AfterViewI tabStepView.click(); await this.delay(200) } - this.isLoading = true; - this.loadingService.setLoading(true); + } + async exportStepView(document: Document): Promise { const elementToConvert = document.querySelector('app-ssg4m-scheme-view') as HTMLElement; const clone = elementToConvert.cloneNode(true) as HTMLElement; @@ -1069,7 +1069,10 @@ export class SubstanceSsg4ManufactureFormComponent implements OnInit, AfterViewI return dataUrl.slice(commaIndex + 1); } - submit(): void { + async submit(): Promise { + await this.expandStepView() + this.isLoading = true; + this.loadingService.setLoading(true); this.approving = false; this.json = this.substanceFormService.cleanSubstance(); @@ -1086,7 +1089,6 @@ export class SubstanceSsg4ManufactureFormComponent implements OnInit, AfterViewI if (this.ssg4mSyntheticPathway == null) { this.ssg4mSyntheticPathway = {}; } - const encodedSvg = await this.exportStepView(document); // Existing Record // get the JSON from the SSG4m Form and store as a Clob into the database @@ -1095,6 +1097,7 @@ export class SubstanceSsg4ManufactureFormComponent implements OnInit, AfterViewI // Save SVG as Clob this.ssg4mSyntheticPathway.sbmsnImage = document.querySelector("#scheme-viz-view").innerHTML; + const encodedSvg = await this.exportStepView(document) this.ssg4mSyntheticPathway.stepViewImage = decodeURIComponent(encodedSvg); // After submitting Save button, the UI waits for 5 seconds to see if it gets a response. From d371478391d734e7dbbf8b79237fd282167b467f Mon Sep 17 00:00:00 2001 From: Jaroslav Iha Date: Wed, 17 Sep 2025 18:47:08 +0200 Subject: [PATCH 101/408] svg save test --- .../substance-ssg4m/substance-ssg4m-form.component.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/app/core/substance-ssg4m/substance-ssg4m-form.component.ts b/src/app/core/substance-ssg4m/substance-ssg4m-form.component.ts index 8271d9225..edd9e47f5 100644 --- a/src/app/core/substance-ssg4m/substance-ssg4m-form.component.ts +++ b/src/app/core/substance-ssg4m/substance-ssg4m-form.component.ts @@ -1047,7 +1047,7 @@ export class SubstanceSsg4ManufactureFormComponent implements OnInit, AfterViewI container.appendChild(clone); document.body.appendChild(container); - await this.delay(5000) + await this.delay(2500) function filter (node) { return (node.tagName !== 'button'); @@ -1070,7 +1070,7 @@ export class SubstanceSsg4ManufactureFormComponent implements OnInit, AfterViewI } async submit(): Promise { - await this.expandStepView() + // await this.expandStepView() this.isLoading = true; this.loadingService.setLoading(true); this.approving = false; @@ -1097,8 +1097,8 @@ export class SubstanceSsg4ManufactureFormComponent implements OnInit, AfterViewI // Save SVG as Clob this.ssg4mSyntheticPathway.sbmsnImage = document.querySelector("#scheme-viz-view").innerHTML; - const encodedSvg = await this.exportStepView(document) - this.ssg4mSyntheticPathway.stepViewImage = decodeURIComponent(encodedSvg); + // const encodedSvg = await this.exportStepView(document) + // this.ssg4mSyntheticPathway.stepViewImage = decodeURIComponent(encodedSvg); // After submitting Save button, the UI waits for 5 seconds to see if it gets a response. // after 5 seconds it displays a warning on the top of the UI form. From 7885470c8eabffe05bd85b0db7db8bb738447cab Mon Sep 17 00:00:00 2001 From: Jaroslav Iha Date: Thu, 18 Sep 2025 11:26:46 +0200 Subject: [PATCH 102/408] svg save test --- .../core/substance-ssg4m/substance-ssg4m-form.component.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/app/core/substance-ssg4m/substance-ssg4m-form.component.ts b/src/app/core/substance-ssg4m/substance-ssg4m-form.component.ts index edd9e47f5..9e46d7f7a 100644 --- a/src/app/core/substance-ssg4m/substance-ssg4m-form.component.ts +++ b/src/app/core/substance-ssg4m/substance-ssg4m-form.component.ts @@ -806,7 +806,7 @@ export class SubstanceSsg4ManufactureFormComponent implements OnInit, AfterViewI }, 5000); } - async validate(validationType?: string): Promise { + validate(validationType?: string): void { if (validationType && validationType === 'approval') { this.approving = true; } else { @@ -826,7 +826,7 @@ export class SubstanceSsg4ManufactureFormComponent implements OnInit, AfterViewI this.isLoading = false; // If there is no validation error, submit/save the records without displaying the warning/validation message. if (this.validationMessages.length === 0 && true === true) { - await this.submit(); + this.submit(); } /* if (this.validationMessages.length === 0 && true === true) { @@ -1069,7 +1069,7 @@ export class SubstanceSsg4ManufactureFormComponent implements OnInit, AfterViewI return dataUrl.slice(commaIndex + 1); } - async submit(): Promise { + submit(): void { // await this.expandStepView() this.isLoading = true; this.loadingService.setLoading(true); From 260a204e07552104c89db5bd3ac197531a7cc48f Mon Sep 17 00:00:00 2001 From: Jaroslav Iha Date: Thu, 18 Sep 2025 14:23:10 +0200 Subject: [PATCH 103/408] code polishing --- .../substance-ssg4m-form.component.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/app/core/substance-ssg4m/substance-ssg4m-form.component.ts b/src/app/core/substance-ssg4m/substance-ssg4m-form.component.ts index 9e46d7f7a..cf17fa875 100644 --- a/src/app/core/substance-ssg4m/substance-ssg4m-form.component.ts +++ b/src/app/core/substance-ssg4m/substance-ssg4m-form.component.ts @@ -806,7 +806,7 @@ export class SubstanceSsg4ManufactureFormComponent implements OnInit, AfterViewI }, 5000); } - validate(validationType?: string): void { + async validate(validationType?: string): Promise { if (validationType && validationType === 'approval') { this.approving = true; } else { @@ -826,7 +826,7 @@ export class SubstanceSsg4ManufactureFormComponent implements OnInit, AfterViewI this.isLoading = false; // If there is no validation error, submit/save the records without displaying the warning/validation message. if (this.validationMessages.length === 0 && true === true) { - this.submit(); + await this.submit(); } /* if (this.validationMessages.length === 0 && true === true) { @@ -1069,8 +1069,8 @@ export class SubstanceSsg4ManufactureFormComponent implements OnInit, AfterViewI return dataUrl.slice(commaIndex + 1); } - submit(): void { - // await this.expandStepView() + async submit(): Promise { + await this.expandStepView() this.isLoading = true; this.loadingService.setLoading(true); this.approving = false; @@ -1097,8 +1097,8 @@ export class SubstanceSsg4ManufactureFormComponent implements OnInit, AfterViewI // Save SVG as Clob this.ssg4mSyntheticPathway.sbmsnImage = document.querySelector("#scheme-viz-view").innerHTML; - // const encodedSvg = await this.exportStepView(document) - // this.ssg4mSyntheticPathway.stepViewImage = decodeURIComponent(encodedSvg); + const encodedSvg = await this.exportStepView(document) + this.ssg4mSyntheticPathway.stepViewImage = decodeURIComponent(encodedSvg); // After submitting Save button, the UI waits for 5 seconds to see if it gets a response. // after 5 seconds it displays a warning on the top of the UI form. From 4d3222518c946e16bbc96359ac0e9a96dee1f70c Mon Sep 17 00:00:00 2001 From: Jaroslav Iha Date: Fri, 19 Sep 2025 14:11:29 +0200 Subject: [PATCH 104/408] add: filter for button --- .../substance-ssg4m/substance-ssg4m-form.component.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/app/core/substance-ssg4m/substance-ssg4m-form.component.ts b/src/app/core/substance-ssg4m/substance-ssg4m-form.component.ts index cf17fa875..97b3c8c92 100644 --- a/src/app/core/substance-ssg4m/substance-ssg4m-form.component.ts +++ b/src/app/core/substance-ssg4m/substance-ssg4m-form.component.ts @@ -1049,8 +1049,8 @@ export class SubstanceSsg4ManufactureFormComponent implements OnInit, AfterViewI await this.delay(2500) - function filter (node) { - return (node.tagName !== 'button'); + function filter (node: HTMLElement) { + return (node.tagName.toLowerCase() !== 'button'); } const options = { filter: filter, @@ -1100,13 +1100,13 @@ export class SubstanceSsg4ManufactureFormComponent implements OnInit, AfterViewI const encodedSvg = await this.exportStepView(document) this.ssg4mSyntheticPathway.stepViewImage = decodeURIComponent(encodedSvg); - // After submitting Save button, the UI waits for 5 seconds to see if it gets a response. + // After submitting Save button, the UI waits for 8 seconds to see if it gets a response. // after 5 seconds it displays a warning on the top of the UI form. setTimeout(() => { if (this.isSavedSuccessful === false) { this.saveDelayedMessage = "Hmm ... this seems to be taking longer than normal, there may be network issues.
    Click here to cancel and continue working on the form. We suggest you save a local copy of the JSON."; } - }, 5000); + }, 8000); this.submitSubscription = this.substanceSsg4mService.saveSsg4m(this.ssg4mSyntheticPathway).pipe(take(1)).subscribe(response => { // Stop the spinner From a7045dce2bef4e919489074f0f1a52ba42160369 Mon Sep 17 00:00:00 2001 From: alx652 <58182629+alx652@users.noreply.github.com> Date: Mon, 29 Sep 2025 20:07:25 -0400 Subject: [PATCH 105/408] Create binary_build.yml --- .github/workflows/binary_build.yml | 80 ++++++++++++++++++++++++++++++ 1 file changed, 80 insertions(+) create mode 100644 .github/workflows/binary_build.yml diff --git a/.github/workflows/binary_build.yml b/.github/workflows/binary_build.yml new file mode 100644 index 000000000..99405b14e --- /dev/null +++ b/.github/workflows/binary_build.yml @@ -0,0 +1,80 @@ +name: Build & Publish Frontend Binaries + + on: + release: + types: [published] # Triggers when a release is published + inputs: + npm_build_string: + description: "NPM build script to run (e.g. build:fda:prod)" + required: true + default: "build:fda:prod" + release_tag: + description: "Release tag to publish" + required: true + default: ${{ github.ref_name }} + frontend_dir: + description: "Path to GSRSFrontend (relative to repo root)" + required: true + default: "." + node_version: + description: "Node.js version" + required: true + default: "18.x" + push: + branches: + - feature/deployable_binary_build + +permissions: + contents: write # needed to create/update releases + +jobs: + build-and-release: + runs-on: + group: ncats-onprem-internal-runners + timeout-minutes: ${{ (matrix.language == 'swift' && 120) || 360 }} + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Use Node.js + uses: actions/setup-node@v4 + with: + node-version: 18.x #${{ inputs.node_version }} + + - name: Run build.sh + working-directory: ${{ inputs.frontend_dir }} + run: bash build.sh + + - name: Run NPM build + working-directory: ${{ inputs.frontend_dir }} + run: npm run build:fda:prod + #${{ inputs.npm_build_string }} + + - name: Zip dist into deployable_binaries.zip + working-directory: . #${{ inputs.frontend_dir }} + run: | + test -d dist || { echo "dist/ not found after build"; exit 1; } + rm -f deployable_binaries.zip + zip -r deployable_binaries.zip dist + + - name: Create/Update GitHub Release and upload asset + uses: softprops/action-gh-release@v2 + with: + tag_name: ${{ inputs.release_tag }} + name: ${{ inputs.release_tag }} + draft: false + prerelease: false + #${{ inputs.frontend_dir }}/ + files: | + deployable_binaries.zip + fail_on_unmatched_files: true + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Print download URL + run: | + echo "Download:" + echo "https://github.com/${{ github.repository }}/releases/download/buildRelease2/deployable_binaries.zip" + + # ${{ inputs.release_tag }}/deployable_binaries.zip" From 60c54560f4c138110ddd56510ad17bf3fe569208 Mon Sep 17 00:00:00 2001 From: alx652 <58182629+alx652@users.noreply.github.com> Date: Mon, 29 Sep 2025 20:49:11 -0400 Subject: [PATCH 106/408] Update binary_build.yml --- .github/workflows/binary_build.yml | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/.github/workflows/binary_build.yml b/.github/workflows/binary_build.yml index 99405b14e..f13a746f4 100644 --- a/.github/workflows/binary_build.yml +++ b/.github/workflows/binary_build.yml @@ -1,8 +1,7 @@ name: Build & Publish Frontend Binaries - - on: - release: - types: [published] # Triggers when a release is published +on: + release: + types: [published] # Triggers when a release is published inputs: npm_build_string: description: "NPM build script to run (e.g. build:fda:prod)" From f32dc01addc8ea6f2135b90cfd32c853c926fb03 Mon Sep 17 00:00:00 2001 From: alx652 <58182629+alx652@users.noreply.github.com> Date: Mon, 29 Sep 2025 21:01:26 -0400 Subject: [PATCH 107/408] Update binary_build.yml --- .github/workflows/binary_build.yml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/binary_build.yml b/.github/workflows/binary_build.yml index f13a746f4..2182234b8 100644 --- a/.github/workflows/binary_build.yml +++ b/.github/workflows/binary_build.yml @@ -7,10 +7,10 @@ on: description: "NPM build script to run (e.g. build:fda:prod)" required: true default: "build:fda:prod" - release_tag: - description: "Release tag to publish" - required: true - default: ${{ github.ref_name }} +# release_tag: +# description: "Release tag to publish" +# required: true +# default: ${{ github.ref_name }} frontend_dir: description: "Path to GSRSFrontend (relative to repo root)" required: true @@ -60,8 +60,8 @@ jobs: - name: Create/Update GitHub Release and upload asset uses: softprops/action-gh-release@v2 with: - tag_name: ${{ inputs.release_tag }} - name: ${{ inputs.release_tag }} + tag_name: ${{ github.ref_name }} + name: ${{ github.ref_name }} draft: false prerelease: false #${{ inputs.frontend_dir }}/ From b913c590ce2e3489796e47b3b5b64835edabc86e Mon Sep 17 00:00:00 2001 From: alx652 <58182629+alx652@users.noreply.github.com> Date: Mon, 29 Sep 2025 21:13:19 -0400 Subject: [PATCH 108/408] Update config.json --- src/app/fda/config/config.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/fda/config/config.json b/src/app/fda/config/config.json index ce4857068..3667d4555 100644 --- a/src/app/fda/config/config.json +++ b/src/app/fda/config/config.json @@ -1,5 +1,5 @@ { - "version": "3.1.3-SNAPSHOT", + "version": "3.1.3-XXXX", "contactEmail": "GSRSSupport@fda.hhs.gov", "displayMatchApplication": "true", "adverseEventShinyHomepageDisplay": "true", From 4b06e55e2476873815053270a74c825388ae0146 Mon Sep 17 00:00:00 2001 From: alx652 <58182629+alx652@users.noreply.github.com> Date: Tue, 30 Sep 2025 13:51:44 -0400 Subject: [PATCH 109/408] Update config.json --- src/app/fda/config/config.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/fda/config/config.json b/src/app/fda/config/config.json index 3667d4555..ce4857068 100644 --- a/src/app/fda/config/config.json +++ b/src/app/fda/config/config.json @@ -1,5 +1,5 @@ { - "version": "3.1.3-XXXX", + "version": "3.1.3-SNAPSHOT", "contactEmail": "GSRSSupport@fda.hhs.gov", "displayMatchApplication": "true", "adverseEventShinyHomepageDisplay": "true", From 453b8dfb0982356b35b6dbe13ec9cc199e2558d3 Mon Sep 17 00:00:00 2001 From: alx652 <58182629+alx652@users.noreply.github.com> Date: Tue, 30 Sep 2025 13:55:52 -0400 Subject: [PATCH 110/408] Update binary_build.yml --- .github/workflows/binary_build.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/binary_build.yml b/.github/workflows/binary_build.yml index 2182234b8..37b135385 100644 --- a/.github/workflows/binary_build.yml +++ b/.github/workflows/binary_build.yml @@ -19,9 +19,9 @@ on: description: "Node.js version" required: true default: "18.x" - push: - branches: - - feature/deployable_binary_build +# push: +# branches: +# - feature/deployable_binary_build permissions: contents: write # needed to create/update releases From 6e1613db24d93c10353fc4744145e8a66ded9736 Mon Sep 17 00:00:00 2001 From: Jaroslav Iha Date: Wed, 1 Oct 2025 11:22:24 +0200 Subject: [PATCH 111/408] fix filter for button --- .../core/substance-ssg4m/substance-ssg4m-form.component.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/app/core/substance-ssg4m/substance-ssg4m-form.component.ts b/src/app/core/substance-ssg4m/substance-ssg4m-form.component.ts index 97b3c8c92..0cb45ee2c 100644 --- a/src/app/core/substance-ssg4m/substance-ssg4m-form.component.ts +++ b/src/app/core/substance-ssg4m/substance-ssg4m-form.component.ts @@ -1050,6 +1050,10 @@ export class SubstanceSsg4ManufactureFormComponent implements OnInit, AfterViewI await this.delay(2500) function filter (node: HTMLElement) { + if (!node.tagName) { + return true; + } + return (node.tagName.toLowerCase() !== 'button'); } const options = { From 69b5116f18a4f8b2d65d2b818c0129a460d3dfa5 Mon Sep 17 00:00:00 2001 From: Mitch Miller Date: Fri, 3 Oct 2025 15:28:48 -0400 Subject: [PATCH 112/408] calling /api/v1/allmyprivs instead of whoami to get roles. Begun --- package.json | 2 +- src/app/core/auth/auth.model.ts | 4 ++ src/app/core/auth/auth.service.ts | 99 +++++++++++++++++++---------- src/app/core/base/base.component.ts | 6 +- 4 files changed, 75 insertions(+), 36 deletions(-) diff --git a/package.json b/package.json index ebf1a19df..f0675d8f2 100644 --- a/package.json +++ b/package.json @@ -71,7 +71,7 @@ "ngx-schema-form": "2.7.0", "primeng": "^13.2.0", "reflect-metadata": "0.1.13", - "rxjs": "7.5.4", + "rxjs": "^7.5.4", "ts-loader": "4.5.0", "util": "0.12.4", "web-animations-js": "2.3.2", diff --git a/src/app/core/auth/auth.model.ts b/src/app/core/auth/auth.model.ts index 8930b64dd..58b908346 100644 --- a/src/app/core/auth/auth.model.ts +++ b/src/app/core/auth/auth.model.ts @@ -48,4 +48,8 @@ export interface UserGroup { _matchContext?: string; } +export interface Privilege { + privilege: string; +} + export type Role = 'Updater'|'Admin'|'Query'|'SuperUpdate'|'DataEntry'|'SuperDataEntry'|'Approver'; diff --git a/src/app/core/auth/auth.service.ts b/src/app/core/auth/auth.service.ts index 2d354cdb4..edd884b2a 100644 --- a/src/app/core/auth/auth.service.ts +++ b/src/app/core/auth/auth.service.ts @@ -1,8 +1,8 @@ import { Injectable, PLATFORM_ID, Inject } from '@angular/core'; import { ConfigService } from '../config/config.service'; -import { Auth, Role, UserGroup } from './auth.model'; -import { Observable, Subject, of } from 'rxjs'; -import { map, take, catchError } from 'rxjs/operators'; +import { Auth, Privilege, Role, UserGroup } from './auth.model'; +import { from, Observable, Subject, throwError, of, firstValueFrom } from 'rxjs'; +import { map, take, catchError, concat, switchMap, tap } from 'rxjs/operators'; import { HttpClient, HttpParams } from '@angular/common/http'; import { isPlatformBrowser } from '@angular/common'; import { UserDownload, AllUserDownloads } from '@gsrs-core/auth/user-downloads/download.model'; @@ -14,46 +14,48 @@ export class AuthService { private _auth: Auth; private _authUpdate: Subject = new Subject(); private isLoading: boolean; + private _privileges: Array = []; constructor( public configService: ConfigService, private http: HttpClient, @Inject(PLATFORM_ID) private platformId: any ) { - this.isLoading = true; - /* - this.fetchAuth().pipe(take(1)).subscribe(auth => { - if (auth && auth.computedToken != null) { - this._auth = auth; - } else { - this._auth = null; - } - this._authUpdate.next(this._auth); - this.isLoading = false; - }, error => { - this._authUpdate.next(null); - this.isLoading = false; - }); - */ - configService.afterLoad().then(cs => { - this.fetchAuth().pipe(take(1)).subscribe(auth => { - if (auth && auth.computedToken != null) { - this._auth = auth; - } else { - this._auth = null; - } - this._authUpdate.next(this._auth); - this.isLoading = false; - }, error => { - this._authUpdate.next(null); - this.isLoading = false; + console.log(`starting AuthService constructor`); + this.isLoading = true; + configService.afterLoad().then(cs => { + this.fetchAuth().pipe(take(1)).subscribe(auth => { + if (auth && auth.computedToken != null) { + this._auth = auth; + } else { + this._auth = null; + } + this._authUpdate.next(this._auth); + this.isLoading = false; + }, error => { + this._authUpdate.next(null); + this.isLoading = false; + }); + console.log('going to call fetchPrivs'); }); - }); + this.fetchPrivs().subscribe({ + next: privs => { + this._privileges = privs; + }, + error: err=>{ + console.error('Error fetching privileges:', err); + }, + complete: () => { + console.log('Privilege fetch complete'); + return this._privileges; + } + }); } - get auth(): Auth { + +get auth(): Auth { return this._auth; - } + } public checkAuth(): Observable { const url = `${(this.configService.configData && this.configService.configData.apiBaseUrl) || '/'}api/v1/`; @@ -246,6 +248,15 @@ export class AuthService { return false; } + async canEditData( ):Promise { + if( this._privileges == null || this._privileges.length === 0) { + const privs = await firstValueFrom(this.fetchPrivs()); + return privs.some(p => p.privilege === 'Edit'); + } + console.log(`starting canEditData. size of privs ${this._privileges.length}`); + return this._privileges != null && this._privileges.some(p=>p.privilege=="Edit"); + } + hasAnyRolesAsync(...roles: Array): Observable { return new Observable(observer => { if (this.auth != null) { @@ -325,6 +336,28 @@ export class AuthService { }); }); } + +private fetchPrivs(): Observable> { + console.log('starting fetchPrivs'); + return from(this.configService.afterLoad()).pipe( + + switchMap(() => { + const baseUrl = this.configService.configData?.apiBaseUrl || '/'; + const url = `${baseUrl}api/v1/allmyprivs`; + console.log(`in switchMap, got url: ${url}`); + return this.http.get(url); + }), + tap(privs => { + this._privileges =privs; + console.log(`received privs ${JSON.stringify(privs)}`); + }), + catchError(err => { + console.error("Authorized error", err); + return throwError(() => err); + }) + ); +} + /* private fetchAuth(): Observable { diff --git a/src/app/core/base/base.component.ts b/src/app/core/base/base.component.ts index 1d12fe9bb..8bde1b7c7 100644 --- a/src/app/core/base/base.component.ts +++ b/src/app/core/base/base.component.ts @@ -139,7 +139,7 @@ export class BaseComponent implements OnInit, OnDestroy { } } - ngOnInit() { + async ngOnInit() { this.showHeaderBar = this.activatedRoute.snapshot.queryParams['header'] || 'true'; this.loadedComponents = this.configService.configData.loadedComponents || null; @@ -174,9 +174,11 @@ export class BaseComponent implements OnInit, OnDestroy { }); this.subscriptions.push(roleSubscription); + this.canRegister=await this.authService.canEditData(); + console.log(`in BaseComponent, canRegister: ${this.canRegister}`); const regSubscription = this.authService.hasAnyRolesAsync('Admin', 'Updater', 'SuperUpdater', 'DataEntry', 'SuperDataEntry').subscribe(response => { - this.canRegister = response; + //this.canRegister = response; }); this.subscriptions.push(regSubscription); this.baseDomain = this.configService.configData.apiUrlDomain; From 49edd67a4ff36af955d57b86f92c4276e59403d4 Mon Sep 17 00:00:00 2001 From: Mitch Miller Date: Fri, 3 Oct 2025 19:29:33 -0400 Subject: [PATCH 113/408] got editing priv check to work in some cases --- src/app/core/auth/auth.service.ts | 14 ++++++++------ .../substance-overview.component.ts | 10 +++++++--- src/app/fda/config/config.json | 14 +++++++------- 3 files changed, 22 insertions(+), 16 deletions(-) diff --git a/src/app/core/auth/auth.service.ts b/src/app/core/auth/auth.service.ts index edd884b2a..68900fc31 100644 --- a/src/app/core/auth/auth.service.ts +++ b/src/app/core/auth/auth.service.ts @@ -250,7 +250,9 @@ get auth(): Auth { async canEditData( ):Promise { if( this._privileges == null || this._privileges.length === 0) { + console.log(`in canEditData, privilege array is empty/null`); const privs = await firstValueFrom(this.fetchPrivs()); + console.log(`in canEditData, receives privs: ${JSON.stringify(privs)}`); return privs.some(p => p.privilege === 'Edit'); } console.log(`starting canEditData. size of privs ${this._privileges.length}`); @@ -337,19 +339,19 @@ get auth(): Auth { }); } -private fetchPrivs(): Observable> { +private fetchPrivs(): Observable { console.log('starting fetchPrivs'); return from(this.configService.afterLoad()).pipe( - switchMap(() => { const baseUrl = this.configService.configData?.apiBaseUrl || '/'; const url = `${baseUrl}api/v1/allmyprivs`; console.log(`in switchMap, got url: ${url}`); - return this.http.get(url); + return this.http.get(url); }), - tap(privs => { - this._privileges =privs; - console.log(`received privs ${JSON.stringify(privs)}`); + map(response => { + const privs: Privilege[] = response.privileges.map(p => ({ privilege: p })); + this._privileges = privs; + return privs; }), catchError(err => { console.error("Authorized error", err); diff --git a/src/app/core/substance-details/substance-overview/substance-overview.component.ts b/src/app/core/substance-details/substance-overview/substance-overview.component.ts index 2b7c9822c..35b6329dd 100644 --- a/src/app/core/substance-details/substance-overview/substance-overview.component.ts +++ b/src/app/core/substance-details/substance-overview/substance-overview.component.ts @@ -71,14 +71,18 @@ export class SubstanceOverviewComponent extends SubstanceCardBase implements OnI this.clasicBaseHref = this.configService.environment.clasicBaseHref; } - ngOnInit() { + async ngOnInit() { - const rolesSubscription = this.authService.hasAnyRolesAsync('updater', 'superUpdater').subscribe(canEdit => { + this.canEdit=await this.authService.canEditData(); + this.isEditable =this.canEdit + && this.substance.substanceClass != null + && (formSections[this.substance.substanceClass.toLowerCase()] != null || formSections[this.substance.substanceClass] != null); + /*const rolesSubscription = this.authService.hasAnyRolesAsync('updater', 'superUpdater').subscribe(canEdit => { this.canEdit = canEdit; this.isEditable = canEdit && this.substance.substanceClass != null && (formSections[this.substance.substanceClass.toLowerCase()] != null || formSections[this.substance.substanceClass] != null); - }); + });*/ const rolesSubscription2 = this.authService.hasAnyRolesAsync('admin').subscribe(canEdit => { this.isAdmin = canEdit; }); diff --git a/src/app/fda/config/config.json b/src/app/fda/config/config.json index ce4857068..78dc11407 100644 --- a/src/app/fda/config/config.json +++ b/src/app/fda/config/config.json @@ -91,16 +91,16 @@ "userRegistration": false }, "services": [ - { "name": "adverse-events", "active": true, "hasEntities": true }, - { "name": "applications", "active": true, "hasEntities": true }, - { "name": "clinical-trials", "active": true, "hasEntities": true }, + { "name": "adverse-events", "active": false, "hasEntities": true }, + { "name": "applications", "active": false, "hasEntities": true }, + { "name": "clinical-trials", "active": false, "hasEntities": true }, { "name": "discovery", "active": false, "hasEntities": false }, { "name": "frontend", "active": true, "hasEntities": false }, { "name": "gateway", "active": true, "hasEntities": false }, - { "name": "impurities", "active": true, "hasEntities": true }, - { "name": "invitro-pharmacology", "active": true, "hasEntities": true }, - { "name": "products", "active": true, "hasEntities": true }, - { "name": "ssg4m", "active": true, "hasEntities": true }, + { "name": "impurities", "active": false, "hasEntities": true }, + { "name": "invitro-pharmacology", "active": false, "hasEntities": true }, + { "name": "products", "active": false, "hasEntities": true }, + { "name": "ssg4m", "active": false, "hasEntities": true }, { "name": "substances", "active": true, "hasEntities": true } ], "usefulLinks": [ From 1f607d3c57d0bf5d9d122ff169fa5cae83a57361 Mon Sep 17 00:00:00 2001 From: Mitch Miller Date: Fri, 3 Oct 2025 22:39:18 -0400 Subject: [PATCH 114/408] made the change to additional files --- .../import-browse.component.html | 10 +++++----- .../import-browse/import-browse.component.ts | 5 ++++- .../cv-import/cv-import.component.html | 4 ---- src/app/core/auth/auth.service.ts | 12 +++++++++++ src/app/core/base/base.component.html | 12 ++--------- src/app/core/base/base.component.ts | 8 ++------ .../substance-overview.component.html | 6 +++--- .../substance-overview.component.ts | 3 +++ .../substance-form.component.html | 4 ++-- .../substance-form.component.ts | 12 +++++------ .../substances-browse.component.html | 20 +++++++++---------- .../substances-browse.component.ts | 14 ++++++++----- 12 files changed, 58 insertions(+), 52 deletions(-) diff --git a/src/app/core/admin/import-browse/import-browse.component.html b/src/app/core/admin/import-browse/import-browse.component.html index 0a431f45f..c9e267e54 100644 --- a/src/app/core/admin/import-browse/import-browse.component.html +++ b/src/app/core/admin/import-browse/import-browse.component.html @@ -556,12 +556,12 @@ -
    + - @@ -603,12 +603,12 @@ Copy Substance to New Form Copy Definition to New Form diff --git a/src/app/core/admin/import-browse/import-browse.component.ts b/src/app/core/admin/import-browse/import-browse.component.ts index 53e548c7c..e36cc5262 100644 --- a/src/app/core/admin/import-browse/import-browse.component.ts +++ b/src/app/core/admin/import-browse/import-browse.component.ts @@ -102,6 +102,7 @@ export class ImportBrowseComponent implements OnInit, AfterViewInit, OnDestroy { private overlayContainer: HTMLElement; private subscriptions: Array = []; isAdmin = false; + canImportData = false; isLoggedIn = false; showExactMatches = false; names: { [substanceId: string]: Array } = {}; @@ -310,7 +311,7 @@ export class ImportBrowseComponent implements OnInit, AfterViewInit, OnDestroy { }); } - ngOnInit() { + async ngOnInit() { this.substances = []; this.records = []; @@ -368,6 +369,8 @@ export class ImportBrowseComponent implements OnInit, AfterViewInit, OnDestroy { this.showAudit = this.authService.hasRoles('admin'); }); + this.canImportData = await this.authService.hasSpecificPrivilege('Import Data'); + this.facetManagerService.registerGetFacetsHandler(this.substanceService.getStagingFacets ); this.environment = this.configService.environment; diff --git a/src/app/core/admin/import-management/cv-import/cv-import.component.html b/src/app/core/admin/import-management/cv-import/cv-import.component.html index 109b104ac..6667abcff 100644 --- a/src/app/core/admin/import-management/cv-import/cv-import.component.html +++ b/src/app/core/admin/import-management/cv-import/cv-import.component.html @@ -12,10 +12,6 @@ value = "{{privateMod}}">{{privateMod}} ({{domain ? 'not in CV' : 'not in field list'}}) -
    diff --git a/src/app/core/auth/auth.service.ts b/src/app/core/auth/auth.service.ts index 68900fc31..470e65381 100644 --- a/src/app/core/auth/auth.service.ts +++ b/src/app/core/auth/auth.service.ts @@ -259,6 +259,18 @@ get auth(): Auth { return this._privileges != null && this._privileges.some(p=>p.privilege=="Edit"); } + + async hasSpecificPrivilege(requestedPrivilege: string ):Promise { + if( this._privileges == null || this._privileges.length === 0) { + console.log(`in hasSpecificPrivilege, privilege array is empty/null`); + const privs = await firstValueFrom(this.fetchPrivs()); + console.log(`in hasSpecificPrivilege, receives privs: ${JSON.stringify(privs)}`); + return privs.some(p => p.privilege === requestedPrivilege); + } + console.log(`starting hasSpecificPrivilege with existing privs. size of privs ${this._privileges.length}`); + return this._privileges != null && this._privileges.some(p=>p.privilege==requestedPrivilege); + } + hasAnyRolesAsync(...roles: Array): Observable { return new Observable(observer => { if (this.auth != null) { diff --git a/src/app/core/base/base.component.html b/src/app/core/base/base.component.html index 25a8e951c..8fa11c818 100644 --- a/src/app/core/base/base.component.html +++ b/src/app/core/base/base.component.html @@ -9,14 +9,6 @@ matTooltipPosition="below">Ver. {{version}}
    - - diff --git a/src/app/core/substance-details/substance-overview/substance-overview.component.ts b/src/app/core/substance-details/substance-overview/substance-overview.component.ts index 35b6329dd..704325afc 100644 --- a/src/app/core/substance-details/substance-overview/substance-overview.component.ts +++ b/src/app/core/substance-details/substance-overview/substance-overview.component.ts @@ -37,6 +37,8 @@ export class SubstanceOverviewComponent extends SubstanceCardBase implements OnI versions: string[] = []; isEditable = false; isAdmin = false; + canRestoreVersions = false; + substanceUpdated = new Subject(); oldUrl: string; baseDomain: string; @@ -74,6 +76,7 @@ export class SubstanceOverviewComponent extends SubstanceCardBase implements OnI async ngOnInit() { this.canEdit=await this.authService.canEditData(); + this.canRestoreVersions = await this.authService.hasSpecificPrivilege("Restore Previous Versions"); this.isEditable =this.canEdit && this.substance.substanceClass != null && (formSections[this.substance.substanceClass.toLowerCase()] != null || formSections[this.substance.substanceClass] != null); diff --git a/src/app/core/substance-form/substance-form.component.html b/src/app/core/substance-form/substance-form.component.html index 53dcff11f..acbcc3275 100644 --- a/src/app/core/substance-form/substance-form.component.html +++ b/src/app/core/substance-form/substance-form.component.html @@ -20,7 +20,7 @@ {{ showSubmissionMessages ? 'Hide' : 'Show' }} messages -
    +
    Advanced Features @@ -59,7 +59,7 @@ Switch primary and alt definitions - + Predict N-Glycosylation Sites diff --git a/src/app/core/substance-form/substance-form.component.ts b/src/app/core/substance-form/substance-form.component.ts index 6fdbecb01..4a0418b32 100644 --- a/src/app/core/substance-form/substance-form.component.ts +++ b/src/app/core/substance-form/substance-form.component.ts @@ -88,8 +88,9 @@ export class SubstanceFormComponent implements OnInit, AfterViewInit, OnDestroy definition: SubstanceFormDefinition; user: string; feature: string; - isAdmin: boolean; - isUpdater: boolean; + canUpdate: boolean; + canMakeAdvancedEdits: boolean; + messageField: string; uuid: string; substanceClass: string; @@ -293,7 +294,7 @@ export class SubstanceFormComponent implements OnInit, AfterViewInit, OnDestroy } - ngOnInit() { + async ngOnInit() { if(this.activatedRoute.snapshot.routeConfig.path === 'structure-features') { this.featuresOnly = true; } @@ -309,9 +310,8 @@ export class SubstanceFormComponent implements OnInit, AfterViewInit, OnDestroy } if (this.configService.configData && this.configService.configData.useApprovalAPI) { this.useApprovalAPI = this.configService.configData.useApprovalAPI; - } - this.isAdmin = this.authService.hasRoles('admin'); - this.isUpdater = this.authService.hasAnyRoles('Updater', 'SuperUpdater'); + } this.canUpdate = await this.authService.hasSpecificPrivilege("Edit"); + this.canMakeAdvancedEdits = await this.authService.hasSpecificPrivilege("Edit Public Data"); this.overlayContainer = this.overlayContainerService.getContainerElement(); this.imported = false; if(this.location.path().includes('chemical-simplified')) { diff --git a/src/app/core/substances-browse/substances-browse.component.html b/src/app/core/substances-browse/substances-browse.component.html index 4b3f106ae..0e8c297fe 100644 --- a/src/app/core/substances-browse/substances-browse.component.html +++ b/src/app/core/substances-browse/substances-browse.component.html @@ -1,6 +1,6 @@ - + Facet View: @@ -10,7 +10,7 @@ - + - - @@ -571,12 +571,12 @@ Copy Substance to New Form Copy Definition to New Form @@ -649,12 +649,12 @@ - - @@ -696,12 +696,12 @@ Copy Substance to New Form Copy Definition to New Form diff --git a/src/app/core/substances-browse/substances-browse.component.ts b/src/app/core/substances-browse/substances-browse.component.ts index 27611f4bd..22e23bc35 100644 --- a/src/app/core/substances-browse/substances-browse.component.ts +++ b/src/app/core/substances-browse/substances-browse.component.ts @@ -102,6 +102,7 @@ export class SubstancesBrowseComponent implements OnInit, AfterViewInit, OnDestr private overlayContainer: HTMLElement; private subscriptions: Array = []; isAdmin = false; + canUpdate = false; isLoggedIn = false; showExactMatches = false; names: { [substanceId: string]: Array } = {}; @@ -217,7 +218,7 @@ export class SubstancesBrowseComponent implements OnInit, AfterViewInit, OnDestr } - ngOnInit() { + async ngOnInit() { this.gaService.sendPageView('Browse Substances'); this.cvService.getDomainVocabulary('CODE_SYSTEM').pipe(take(1)).subscribe(response => { this.codeSystem = response['CODE_SYSTEM'].dictionary; @@ -284,11 +285,14 @@ export class SubstancesBrowseComponent implements OnInit, AfterViewInit, OnDestr } else { this.showDeprecated = false; } - this.isAdmin = this.authService.hasAnyRoles('Updater', 'SuperUpdater'); - this.showAudit = this.authService.hasRoles('admin'); - this.showUserLists = this.authService.hasAnyRoles('Updater', 'SuperUpdater', 'DataEntry'); - + }); + this.canUpdate = await this.authService.hasSpecificPrivilege('Edit'); + this.showUserLists=this.canUpdate; + //todo: evaluate this! + this.showAudit =await this.authService.hasSpecificPrivilege('Restore Previous Versions'); + + if (deprecated && deprecated === 'true' && this.showAudit) { this.showDeprecated = true; } From e248743ed3449e7ebe026dd9aaef5f328bba6104 Mon Sep 17 00:00:00 2001 From: Mitch Miller Date: Sat, 4 Oct 2025 17:37:28 -0400 Subject: [PATCH 115/408] similar changes to several additional files --- .../import-browse.component.html | 277 ------------------ .../import-browse/import-browse.component.ts | 5 +- .../user-query-list-dialog.component.html | 4 +- .../user-query-list-dialog.component.ts | 11 +- .../substance-hierarchy.component.html | 4 +- .../substance-hierarchy.component.ts | 6 +- .../cv-input/cv-input.component.html | 2 +- .../cv-input/cv-input.component.ts | 6 +- ...lationships-download-button.component.html | 2 +- ...relationships-download-button.component.ts | 15 +- .../substance-ssg2-form.component.html | 99 ------- .../substance-hierarchy.component.html | 4 +- .../substance-hierarchy.component.ts | 6 +- .../substance-summary-card.component.html | 27 +- .../substance-summary-card.component.ts | 16 +- .../substances-browse.component.html | 8 - .../adverse-events-cvm-browse.component.html | 2 +- .../adverse-events-cvm-browse.component.ts | 7 +- 18 files changed, 39 insertions(+), 462 deletions(-) diff --git a/src/app/core/admin/import-browse/import-browse.component.html b/src/app/core/admin/import-browse/import-browse.component.html index c9e267e54..53dcbdfec 100644 --- a/src/app/core/admin/import-browse/import-browse.component.html +++ b/src/app/core/admin/import-browse/import-browse.component.html @@ -16,72 +16,6 @@ click here to clear all your search criteria.
    - -
    @@ -116,55 +50,6 @@
    -
    @@ -219,10 +104,6 @@
    - - @@ -278,41 +159,6 @@
    - - - - - -
    @@ -372,129 +218,6 @@
    -
    diff --git a/src/app/core/admin/import-browse/import-browse.component.ts b/src/app/core/admin/import-browse/import-browse.component.ts index e36cc5262..44356697a 100644 --- a/src/app/core/admin/import-browse/import-browse.component.ts +++ b/src/app/core/admin/import-browse/import-browse.component.ts @@ -101,7 +101,6 @@ export class ImportBrowseComponent implements OnInit, AfterViewInit, OnDestroy { showAudit: boolean; private overlayContainer: HTMLElement; private subscriptions: Array = []; - isAdmin = false; canImportData = false; isLoggedIn = false; showExactMatches = false; @@ -365,11 +364,9 @@ export class ImportBrowseComponent implements OnInit, AfterViewInit, OnDestroy { } else { this.showDeprecated = true; } - this.isAdmin = this.authService.hasAnyRoles('Updater', 'SuperUpdater'); - this.showAudit = this.authService.hasRoles('admin'); - }); this.canImportData = await this.authService.hasSpecificPrivilege('Import Data'); + this.showAudit = await this.authService.hasSpecificPrivilege('Restore Previous Versions'); this.facetManagerService.registerGetFacetsHandler(this.substanceService.getStagingFacets ); diff --git a/src/app/core/bulk-search/user-query-list-dialog/user-query-list-dialog.component.html b/src/app/core/bulk-search/user-query-list-dialog/user-query-list-dialog.component.html index c5d482f7f..839612a37 100644 --- a/src/app/core/bulk-search/user-query-list-dialog/user-query-list-dialog.component.html +++ b/src/app/core/bulk-search/user-query-list-dialog/user-query-list-dialog.component.html @@ -5,10 +5,10 @@

    Saved Lists {{setUser ? ' - ' + setUser : ''}}

    diff --git a/src/app/core/bulk-search/user-query-list-dialog/user-query-list-dialog.component.ts b/src/app/core/bulk-search/user-query-list-dialog/user-query-list-dialog.component.ts index 911ae2b17..a4e43c997 100644 --- a/src/app/core/bulk-search/user-query-list-dialog/user-query-list-dialog.component.ts +++ b/src/app/core/bulk-search/user-query-list-dialog/user-query-list-dialog.component.ts @@ -39,7 +39,7 @@ export class UserQueryListDialogComponent implements OnInit { users = []; setUser: string; identifier: string; - isAdmin = false; + canManageListsForOthers = false; etagIDs = []; uniqueRecords = []; disabled = false; @@ -70,7 +70,7 @@ export class UserQueryListDialogComponent implements OnInit { } - ngOnInit(): void { + async ngOnInit() { this.substanceService.getAllByEtag(this.etag).subscribe(result => { if(result.content) { result.content.forEach(record => { @@ -82,19 +82,16 @@ export class UserQueryListDialogComponent implements OnInit { this.authService.checkAuth().subscribe(response => { this.setUser = response.identifier; this.identifier = response.identifier; - response.roles.forEach(role => { - if (role === 'Admin') { - this.isAdmin = true; - } - }); }); this.getUserLists(); if (this.view === 'single') { this.useDraft(this.activeName); } + this.canManageListsForOthers = await this.authService.hasSpecificPrivilege('Manage Others Lists'); } + viewLists(): void { this.getUserLists(); this.showAddButtons = false; diff --git a/src/app/core/substance-details/substance-hierarchy/substance-hierarchy.component.html b/src/app/core/substance-details/substance-hierarchy/substance-hierarchy.component.html index bdcc19c12..0e841042c 100644 --- a/src/app/core/substance-details/substance-hierarchy/substance-hierarchy.component.html +++ b/src/app/core/substance-details/substance-hierarchy/substance-hierarchy.component.html @@ -13,7 +13,7 @@ - +
    @@ -42,7 +42,7 @@ [routerLink]="['/substances', node.value.refuuid || '']" [innerHTML] = "node.value.refPname"> - +
    diff --git a/src/app/core/substance-details/substance-hierarchy/substance-hierarchy.component.ts b/src/app/core/substance-details/substance-hierarchy/substance-hierarchy.component.ts index 32b609579..c710e9152 100644 --- a/src/app/core/substance-details/substance-hierarchy/substance-hierarchy.component.ts +++ b/src/app/core/substance-details/substance-hierarchy/substance-hierarchy.component.ts @@ -28,10 +28,10 @@ export class SubstanceHierarchyComponent extends SubstanceCardBase implements On dataSource = new MatTreeNestedDataSource(); selfNode: HierarchyNode; activeNode: any; - isAdmin: boolean; + canEdit: boolean = false; hasChild = (_: number, node: any) => !!node.children && node.children.length > 0; - ngOnInit() { + async ngOnInit() { this.uuid = this.substance.uuid; this.name = this.substance._nameHTML; this.selfNode = { @@ -51,7 +51,7 @@ export class SubstanceHierarchyComponent extends SubstanceCardBase implements On }, error => { this.loadHierarchy([this.selfNode]); }); - this.isAdmin = this.authService.hasAnyRoles('Admin', 'Updater', 'SuperUpdater'); + this.canEdit = await this.authService.hasSpecificPrivilege('Edit') } loadHierarchy(orig: any): void { diff --git a/src/app/core/substance-form/cv-input/cv-input.component.html b/src/app/core/substance-form/cv-input/cv-input.component.html index 4064754c1..287bba331 100644 --- a/src/app/core/substance-form/cv-input/cv-input.component.html +++ b/src/app/core/substance-form/cv-input/cv-input.component.html @@ -11,7 +11,7 @@ Other (New Value) - add diff --git a/src/app/core/substance-form/cv-input/cv-input.component.ts b/src/app/core/substance-form/cv-input/cv-input.component.ts index 339e7e6e0..e57a12470 100644 --- a/src/app/core/substance-form/cv-input/cv-input.component.ts +++ b/src/app/core/substance-form/cv-input/cv-input.component.ts @@ -34,7 +34,7 @@ export class CvInputComponent implements OnInit, OnDestroy { dictionary: any; private overlayContainer: HTMLElement; private subscriptions: Array = []; - isAdmin: boolean; + canManageCVs: boolean = false; constructor( public cvService: ControlledVocabularyService, @@ -46,7 +46,7 @@ export class CvInputComponent implements OnInit, OnDestroy { private configService: ConfigService ) { } - ngOnInit() { + async ngOnInit() { if (this.vocabulary) { this.vocabulary = this.addOtherOption(this.vocabulary, this.privateMod); this.sortFromConfig(); @@ -75,7 +75,7 @@ export class CvInputComponent implements OnInit, OnDestroy { } this.overlayContainer = this.overlayContainerService.getContainerElement(); - this.isAdmin = this.authService.hasRoles('admin'); + this.canManageCVs = await this.authService.hasSpecificPrivilege('Manage CVs'); } ngOnDestroy() { diff --git a/src/app/core/substance-form/relationships/relationships-download-button/relationships-download-button.component.html b/src/app/core/substance-form/relationships/relationships-download-button/relationships-download-button.component.html index 3960b5884..edbbd5ca2 100644 --- a/src/app/core/substance-form/relationships/relationships-download-button/relationships-download-button.component.html +++ b/src/app/core/substance-form/relationships/relationships-download-button/relationships-download-button.component.html @@ -1,6 +1,6 @@
    -
    +
    -
    @@ -49,7 +49,7 @@ [routerLink]="['/substances', node.value.refuuid || '']" [innerHTML] = "node.value.refPname"> - +
    diff --git a/src/app/core/substances-browse/substance-hierarchy/substance-hierarchy.component.ts b/src/app/core/substances-browse/substance-hierarchy/substance-hierarchy.component.ts index ce0a1b08a..9fcd76ebb 100644 --- a/src/app/core/substances-browse/substance-hierarchy/substance-hierarchy.component.ts +++ b/src/app/core/substances-browse/substance-hierarchy/substance-hierarchy.component.ts @@ -19,7 +19,7 @@ export class SubstanceHierarchyComponent implements OnInit { dataSource = new MatTreeNestedDataSource(); selfNode: HierarchyNode; activeNode: any; - isAdmin: boolean; + canEdit: boolean; loading = true; hasChild = (_: number, node: any) => !!node.children && node.children.length > 0; constructor( @@ -27,7 +27,7 @@ export class SubstanceHierarchyComponent implements OnInit { private authService: AuthService ) { } - ngOnInit() { + async ngOnInit() { this.selfNode = { 'id': 0, 'type': 'ROOT', @@ -46,7 +46,7 @@ export class SubstanceHierarchyComponent implements OnInit { this.loadHierarchy([this.selfNode]); }); - this.isAdmin = this.authService.hasAnyRoles('Admin', 'Updater', 'SuperUpdater'); + this.canEdit = await this.authService.hasSpecificPrivilege("Edit"); } loadHierarchy(orig: any): void { diff --git a/src/app/core/substances-browse/substance-summary-card/substance-summary-card.component.html b/src/app/core/substances-browse/substance-summary-card/substance-summary-card.component.html index 5f48d1e3e..7cd1cbcda 100644 --- a/src/app/core/substances-browse/substance-summary-card/substance-summary-card.component.html +++ b/src/app/core/substances-browse/substance-summary-card/substance-summary-card.component.html @@ -9,7 +9,7 @@
    -
    +
    Add to List @@ -23,7 +23,7 @@ - + @@ -33,7 +33,6 @@

    -
    @@ -84,34 +83,17 @@ - - - + *ngIf="canUpdate" mat-icon-button [routerLink]="['/substances', substance.uuid, 'edit']"> - - - -
    -
    diff --git a/src/app/core/substances-browse/substance-summary-card/substance-summary-card.component.ts b/src/app/core/substances-browse/substance-summary-card/substance-summary-card.component.ts index 462cd1b50..5279c328c 100644 --- a/src/app/core/substances-browse/substance-summary-card/substance-summary-card.component.ts +++ b/src/app/core/substances-browse/substance-summary-card/substance-summary-card.component.ts @@ -37,8 +37,8 @@ export class SubstanceSummaryCardComponent implements OnInit { private privateSubstance: SubstanceSummary; @Output() openImage = new EventEmitter(); @Input() showAudit: boolean; - isAdmin = false; //this shouldn't be called "isAdmin", it's typically used to mean "canUpdate". Should fix for future devs. canCreate = false; //meant to allow creating new records + canUpdate = false; subunits?: Array; @ViewChild(CardDynamicSectionDirective, {static: true}) dynamicContentContainer: CardDynamicSectionDirective; @Input() codeSystemNames?: Array; @@ -78,18 +78,10 @@ export class SubstanceSummaryCardComponent implements OnInit { @Inject(DYNAMIC_COMPONENT_MANIFESTS) private dynamicContentItems: DynamicComponentManifest[] ) { } - ngOnInit() { + async ngOnInit() { this.overlayContainer = this.overlayContainerService.getContainerElement(); - this.authService.hasAnyRolesAsync('Updater', 'SuperUpdater', 'Approver', 'admin').pipe(take(1)).subscribe(response => { - if (response) { - this.isAdmin = response; - } - }); - this.authService.hasAnyRolesAsync('DataEntry', 'SuperDataEntry', 'admin').pipe(take(1)).subscribe(response => { - if (response) { - this.canCreate = response; - } - }); + this.canCreate = await this.authService.hasSpecificPrivilege("Create"); + this.canUpdate = await this.authService.hasSpecificPrivilege("Edit"); if (this.substance.protein) { this.subunits = this.substance.protein.subunits; this.getAlignments(); diff --git a/src/app/core/substances-browse/substances-browse.component.html b/src/app/core/substances-browse/substances-browse.component.html index 0e8c297fe..9ca89f0d4 100644 --- a/src/app/core/substances-browse/substances-browse.component.html +++ b/src/app/core/substances-browse/substances-browse.component.html @@ -151,9 +151,6 @@
    Structure Search is Processing... -
    - - @@ -762,7 +755,6 @@

    {{privateSearchType | titlecase }} Search is Processing...


    Returning all results may take some time depending on the query.
    The results will refresh once the search is complete.
    You may close this dialog to browse the incomplete results.


    -
    diff --git a/src/app/fda/adverse-event/adverse-events-cvm-browse/adverse-events-cvm-browse.component.html b/src/app/fda/adverse-event/adverse-events-cvm-browse/adverse-events-cvm-browse.component.html index 2ca9ce44d..a3d55f00c 100644 --- a/src/app/fda/adverse-event/adverse-events-cvm-browse/adverse-events-cvm-browse.component.html +++ b/src/app/fda/adverse-event/adverse-events-cvm-browse/adverse-events-cvm-browse.component.html @@ -131,7 +131,7 @@ --> -
    +
    -
    +
    Advanced Features From b5791db835207ac720d0d403e79a49ad825e6436 Mon Sep 17 00:00:00 2001 From: Jaroslav Iha Date: Tue, 7 Oct 2025 11:41:39 +0200 Subject: [PATCH 118/408] allow UUID generator for pfda version --- src/app/core/substance-form/substance-form.component.html | 4 ++-- src/app/core/substance-form/substance-form.component.ts | 2 ++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/app/core/substance-form/substance-form.component.html b/src/app/core/substance-form/substance-form.component.html index d1615e3f0..4c566991c 100644 --- a/src/app/core/substance-form/substance-form.component.html +++ b/src/app/core/substance-form/substance-form.component.html @@ -20,7 +20,7 @@ {{ showSubmissionMessages ? 'Hide' : 'Show' }} messages -
    +
    Advanced Features @@ -59,7 +59,7 @@ Switch primary and alt definitions - + Predict N-Glycosylation Sites diff --git a/src/app/core/substance-form/substance-form.component.ts b/src/app/core/substance-form/substance-form.component.ts index fdd05fefa..fb32ea43c 100644 --- a/src/app/core/substance-form/substance-form.component.ts +++ b/src/app/core/substance-form/substance-form.component.ts @@ -91,6 +91,7 @@ export class SubstanceFormComponent implements OnInit, AfterViewInit, OnDestroy feature: string; isAdmin: boolean; isUpdater: boolean; + isPfdaVersion: boolean = false; messageField: string; uuid: string; substanceClass: string; @@ -310,6 +311,7 @@ export class SubstanceFormComponent implements OnInit, AfterViewInit, OnDestroy } this.isAdmin = this.authService.hasRoles('admin'); this.isUpdater = this.authService.hasAnyRoles('Updater', 'SuperUpdater'); + this.isPfdaVersion = this.configService.configData.isPfdaVersion; this.overlayContainer = this.overlayContainerService.getContainerElement(); this.imported = false; if(this.location.path().includes('chemical-simplified')) { From cc752747640685ecc70a3fdd7ae78f5b0f22d387 Mon Sep 17 00:00:00 2001 From: Jaroslav Iha Date: Tue, 7 Oct 2025 16:09:16 +0200 Subject: [PATCH 119/408] disable save ssg4m btn for non logged users --- .../core/substance-ssg4m/substance-ssg4m-form.component.html | 2 +- src/app/core/substance-ssg4m/substance-ssg4m-form.component.ts | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/app/core/substance-ssg4m/substance-ssg4m-form.component.html b/src/app/core/substance-ssg4m/substance-ssg4m-form.component.html index 0560b73ad..09715caec 100644 --- a/src/app/core/substance-ssg4m/substance-ssg4m-form.component.html +++ b/src/app/core/substance-ssg4m/substance-ssg4m-form.component.html @@ -51,7 +51,7 @@ + [disabled]="showFormReadOnly === 'true' || isAuthenticated === false">Save - + matTooltip='copy Impurities to new registration form' *ngIf="canEdit">
    diff --git a/src/app/fda/impurities/impurities-details/impurities-details.component.ts b/src/app/fda/impurities/impurities-details/impurities-details.component.ts index f001fa9cd..090a1f017 100644 --- a/src/app/fda/impurities/impurities-details/impurities-details.component.ts +++ b/src/app/fda/impurities/impurities-details/impurities-details.component.ts @@ -29,7 +29,7 @@ export class ImpuritiesDetailsComponent implements OnInit, OnDestroy { impurities: Impurities; substanceName = ''; flagIconSrcPath: string; - isAdmin = false; + canEdit: boolean = false; updateApplicationUrl: string; message = ''; subRelationship: any; @@ -54,14 +54,10 @@ export class ImpuritiesDetailsComponent implements OnInit, OnDestroy { private titleService: Title ) { } - ngOnInit() { + async ngOnInit() { this.loadingService.setLoading(true); - const rolesSubscription = this.authService.hasAnyRolesAsync('admin', 'updater', 'superUpdater').subscribe(canEdit => { - this.isAdmin = canEdit; - }); - this.subscriptions.push(rolesSubscription); - + this.canEdit = await this.authService.hasSpecificPrivilege('Edit'); this.id = this.activatedRoute.snapshot.params['id']; if (this.id != null) { this.getImpurities(); diff --git a/src/app/fda/impurities/impurities-form/can-activate-register-impurities-form.component.ts b/src/app/fda/impurities/impurities-form/can-activate-register-impurities-form.component.ts index ac906b34e..4b2854b1f 100644 --- a/src/app/fda/impurities/impurities-form/can-activate-register-impurities-form.component.ts +++ b/src/app/fda/impurities/impurities-form/can-activate-register-impurities-form.component.ts @@ -19,15 +19,13 @@ export class CanActivateRegisterImpuritiesFormComponent implements CanActivate { return new Observable(observer => { this.authService.getAuth().pipe(take(1)).subscribe(auth => { if (auth) { - this.authService.hasAnyRolesAsync('DataEntry', 'SuperDataEntry').pipe(take(1)).subscribe(response => { - if (response) { - observer.next(true); + if(this.authService.hasSpecificPrivilege('Edit')){ + observer.next(true); observer.complete(); } else { observer.next(this.router.parseUrl('/home')); observer.complete(); - } - }); + } } else { const navigationExtras: NavigationExtras = { queryParams: { diff --git a/src/app/fda/impurities/impurities-form/can-activate-update-impurities-form.component.ts b/src/app/fda/impurities/impurities-form/can-activate-update-impurities-form.component.ts index 614f98f00..e6a9e61df 100644 --- a/src/app/fda/impurities/impurities-form/can-activate-update-impurities-form.component.ts +++ b/src/app/fda/impurities/impurities-form/can-activate-update-impurities-form.component.ts @@ -19,16 +19,14 @@ export class CanActivateUpdateImpuritiesFormComponent implements CanActivate { return new Observable(observer => { this.authService.getAuth().pipe(take(1)).subscribe(auth => { if (auth) { - this.authService.hasAnyRolesAsync('Updater', 'SuperUpdater').pipe(take(1)).subscribe(response => { - if (response) { - observer.next(true); - observer.complete(); - } else { - observer.next(this.router.parseUrl('/home')); - observer.complete(); - } - }); - } else { + if( this.authService.hasSpecificPrivilege('Edit')){ + observer.next(true); + observer.complete(); + } else { + observer.next(this.router.parseUrl('/home')); + observer.complete(); + } + } else { const navigationExtras: NavigationExtras = { queryParams: { path: state.url diff --git a/src/app/fda/impurities/impurities-form/impurities-form.component.html b/src/app/fda/impurities/impurities-form/impurities-form.component.html index 4456e1bbe..140f1d090 100644 --- a/src/app/fda/impurities/impurities-form/impurities-form.component.html +++ b/src/app/fda/impurities/impurities-form/impurities-form.component.html @@ -36,7 +36,7 @@ View Impurities     - + @@ -9,7 +9,7 @@
    -
    +
    @@ -294,7 +294,7 @@


    -
    +
    Please Login to import Assay Data. diff --git a/src/app/fda/invitro-pharmacology/invitro-pharmacology-assay-data-import/invitro-pharmacology-assay-data-import.component.ts b/src/app/fda/invitro-pharmacology/invitro-pharmacology-assay-data-import/invitro-pharmacology-assay-data-import.component.ts index 69344dbee..d1e652e5e 100644 --- a/src/app/fda/invitro-pharmacology/invitro-pharmacology-assay-data-import/invitro-pharmacology-assay-data-import.component.ts +++ b/src/app/fda/invitro-pharmacology/invitro-pharmacology-assay-data-import/invitro-pharmacology-assay-data-import.component.ts @@ -62,7 +62,7 @@ export class InvitroPharmacologyAssayDataImportComponent implements OnInit { isAllRecordValidated = false; isAllRecordSaved = false; isLoading = false; - isAdmin = false; + canCreate: boolean = false; targetNameCheckCompleted = false; humanHomologCheckCompleted = false; @@ -95,12 +95,8 @@ export class InvitroPharmacologyAssayDataImportComponent implements OnInit { private invitroPharmacologyService: InvitroPharmacologyService ) { } - ngOnInit(): void { - // Check if user has either Admin or Updater role - this.authService.hasAnyRolesAsync('DataEntry', 'SuperDataEntry', 'Admin').subscribe(response => { - this.isAdmin = response; - }); - + async ngOnInit() { + this.canCreate = await this.authService.hasSpecificPrivilege('Create'); this.overlayContainer = this.overlayContainerService.getContainerElement(); this.titleService.setTitle("IVP Import Assay Data"); diff --git a/src/app/fda/invitro-pharmacology/invitro-pharmacology-details/invitro-pharmacology-details.component.ts b/src/app/fda/invitro-pharmacology/invitro-pharmacology-details/invitro-pharmacology-details.component.ts index 978eec366..b41dbcf8d 100644 --- a/src/app/fda/invitro-pharmacology/invitro-pharmacology-details/invitro-pharmacology-details.component.ts +++ b/src/app/fda/invitro-pharmacology/invitro-pharmacology-details/invitro-pharmacology-details.component.ts @@ -44,7 +44,7 @@ export class InvitroPharmacologyDetailsComponent implements OnInit, OnDestroy { jsonFileName: string; flagIconSrcPath: string; - isAdmin = false; + canEdit: boolean = false; private overlayContainer: HTMLElement; private subscriptions: Array = []; @@ -63,15 +63,11 @@ export class InvitroPharmacologyDetailsComponent implements OnInit, OnDestroy { private dialog: MatDialog ) { } - ngOnInit() { + async ngOnInit() { this.overlayContainer = this.overlayContainerService.getContainerElement(); this.loadingService.setLoading(true); - const rolesSubscription = this.authService.hasAnyRolesAsync('admin', 'updater', 'superUpdater').subscribe(canEdit => { - this.isAdmin = canEdit; - }); - this.subscriptions.push(rolesSubscription); - + this.canEdit = await this.authService.hasSpecificPrivilege('Edit'); this.id = this.activatedRoute.snapshot.params['id']; if (this.id != null) { this.getInvitroPharmacology(); From 4dc4c42b7b77a739fc0131048eb6665db7245ae4 Mon Sep 17 00:00:00 2001 From: Mitch Miller Date: Thu, 9 Oct 2025 20:20:53 -0400 Subject: [PATCH 121/408] isAdmin replaced in another 24 files --- ...ter-invitro-pharmacology-form.component.ts | 6 ++--- ...ate-invitro-pharmacology-form.component.ts | 16 ++++++------- ...vitro-pharmacology-assay-form.component.ts | 6 ----- ...ro-pharmacology-assayset-form.component.ts | 10 +++----- ...ology-screening-data-import.component.html | 10 ++------ ...acology-screening-data-import.component.ts | 9 +++---- .../product-details-base.component.ts | 4 ++-- .../product-details.component.html | 4 ++-- .../product-details.component.ts | 6 +---- .../product-elist-details.component.ts | 2 +- ...ctivate-register-product-form.component.ts | 6 ++--- ...-activate-update-product-form.component.ts | 16 ++++++------- .../show-application-toggle.component.html | 12 +++++----- .../show-application-toggle.component.ts | 14 ++++------- .../substance-adverseeventcvm.component.html | 18 +------------- .../substance-adverseeventcvm.component.ts | 8 +++---- .../substance-adverseeventdme.component.html | 17 +------------ .../substance-adverseeventdme.component.ts | 9 +++---- .../substance-adverseeventpt.component.html | 9 +------ .../substance-adverseeventpt.component.ts | 10 ++++---- .../substance-application.component.html | 12 ++-------- .../substance-application.component.ts | 9 +++---- .../substance-products.component.html | 24 ++----------------- .../substance-products.component.ts | 10 ++++---- 24 files changed, 70 insertions(+), 177 deletions(-) diff --git a/src/app/fda/invitro-pharmacology/invitro-pharmacology-form/can-activate-register-invitro-pharmacology-form.component.ts b/src/app/fda/invitro-pharmacology/invitro-pharmacology-form/can-activate-register-invitro-pharmacology-form.component.ts index e5efef155..55345efc9 100644 --- a/src/app/fda/invitro-pharmacology/invitro-pharmacology-form/can-activate-register-invitro-pharmacology-form.component.ts +++ b/src/app/fda/invitro-pharmacology/invitro-pharmacology-form/can-activate-register-invitro-pharmacology-form.component.ts @@ -20,15 +20,13 @@ export class CanActivateRegisterInvitroPharmacologyFormComponent implements CanA return new Observable(observer => { this.authService.getAuth().pipe(take(1)).subscribe(auth => { if (auth) { - this.authService.hasAnyRolesAsync('DataEntry', 'SuperDataEntry').pipe(take(1)).subscribe(response => { - if (response) { + if(this.authService.hasSpecificPrivilege('Edit')) { observer.next(true); observer.complete(); } else { observer.next(this.router.parseUrl('/browse-invitro-pharm')); observer.complete(); - } - }); + } } else { const navigationExtras: NavigationExtras = { queryParams: { diff --git a/src/app/fda/invitro-pharmacology/invitro-pharmacology-form/can-activate-update-invitro-pharmacology-form.component.ts b/src/app/fda/invitro-pharmacology/invitro-pharmacology-form/can-activate-update-invitro-pharmacology-form.component.ts index d39b9c631..86de1f37a 100644 --- a/src/app/fda/invitro-pharmacology/invitro-pharmacology-form/can-activate-update-invitro-pharmacology-form.component.ts +++ b/src/app/fda/invitro-pharmacology/invitro-pharmacology-form/can-activate-update-invitro-pharmacology-form.component.ts @@ -19,15 +19,13 @@ export class CanActivateUpdateInvitroPharmacologyFormComponent implements CanAct return new Observable(observer => { this.authService.getAuth().pipe(take(1)).subscribe(auth => { if (auth) { - this.authService.hasAnyRolesAsync('Updater', 'SuperUpdater').pipe(take(1)).subscribe(response => { - if (response) { - observer.next(true); - observer.complete(); - } else { - observer.next(this.router.parseUrl('/browse-invitro-pharm')); - observer.complete(); - } - }); + if( this.authService.hasSpecificPrivilege('Edit')){ + observer.next(true); + observer.complete(); + } else { + observer.next(this.router.parseUrl('/browse-invitro-pharm')); + observer.complete(); + } } else { const navigationExtras: NavigationExtras = { queryParams: { diff --git a/src/app/fda/invitro-pharmacology/invitro-pharmacology-form/invitro-pharmacology-assay-form/invitro-pharmacology-assay-form.component.ts b/src/app/fda/invitro-pharmacology/invitro-pharmacology-form/invitro-pharmacology-assay-form/invitro-pharmacology-assay-form.component.ts index 152a563c4..72c508250 100644 --- a/src/app/fda/invitro-pharmacology/invitro-pharmacology-form/invitro-pharmacology-assay-form/invitro-pharmacology-assay-form.component.ts +++ b/src/app/fda/invitro-pharmacology/invitro-pharmacology-form/invitro-pharmacology-assay-form/invitro-pharmacology-assay-form.component.ts @@ -68,7 +68,6 @@ export class InvitroPharmacologyAssayFormComponent implements OnInit, OnDestroy downloadJsonHref: any; jsonFileName: string; - isAdmin = false; isLoading = true; username = null; title = null; @@ -95,11 +94,6 @@ export class InvitroPharmacologyAssayFormComponent implements OnInit, OnDestroy this.loadingService.setLoading(this.isLoading); this.overlayContainer = this.overlayContainerService.getContainerElement(); - // Check if user has either Admin or Updater role - this.authService.hasAnyRolesAsync('DataEntry', 'SuperDataEntry', 'Admin').subscribe(response => { - this.isAdmin = response; - }); - // Get Username this.username = this.authService.getUser(); diff --git a/src/app/fda/invitro-pharmacology/invitro-pharmacology-form/invitro-pharmacology-assayset-form/invitro-pharmacology-assayset-form.component.ts b/src/app/fda/invitro-pharmacology/invitro-pharmacology-form/invitro-pharmacology-assayset-form/invitro-pharmacology-assayset-form.component.ts index d496f6170..17a4d1344 100644 --- a/src/app/fda/invitro-pharmacology/invitro-pharmacology-form/invitro-pharmacology-assayset-form/invitro-pharmacology-assayset-form.component.ts +++ b/src/app/fda/invitro-pharmacology/invitro-pharmacology-form/invitro-pharmacology-assayset-form/invitro-pharmacology-assayset-form.component.ts @@ -64,7 +64,7 @@ export class InvitroPharmacologyAssaysetFormComponent implements OnInit { newSavedAssaySet: any; title: string; message: string; - isAdmin = false; + canCreate = false; username = null; isLoading = false; isBuildFromExistingSet = false; @@ -100,13 +100,9 @@ export class InvitroPharmacologyAssaysetFormComponent implements OnInit { private invitroPharmacologyService: InvitroPharmacologyService ) { } - ngOnInit(): void { - - // Check if user has either Admin or Updater role - this.authService.hasAnyRolesAsync('DataEntry', 'SuperDataEntry', 'Admin').subscribe(response => { - this.isAdmin = response; - }); + async ngOnInit(){ + this.canCreate = await this.authService.hasSpecificPrivilege('Create'); // Get Username this.username = this.authService.getUser(); diff --git a/src/app/fda/invitro-pharmacology/invitro-pharmacology-screening-data-import/invitro-pharmacology-screening-data-import.component.html b/src/app/fda/invitro-pharmacology/invitro-pharmacology-screening-data-import/invitro-pharmacology-screening-data-import.component.html index 032cb7f0f..efe0660f4 100644 --- a/src/app/fda/invitro-pharmacology/invitro-pharmacology-screening-data-import/invitro-pharmacology-screening-data-import.component.html +++ b/src/app/fda/invitro-pharmacology/invitro-pharmacology-screening-data-import/invitro-pharmacology-screening-data-import.component.html @@ -8,13 +8,7 @@ -
    - +
    -
    +
    -
    -
    - -
    diff --git a/src/app/fda/substance-details/substance-products/substance-adverseevent/adverseeventcvm/substance-adverseeventcvm.component.ts b/src/app/fda/substance-details/substance-products/substance-adverseevent/adverseeventcvm/substance-adverseeventcvm.component.ts index 1b34b9fbb..25f5d71a9 100644 --- a/src/app/fda/substance-details/substance-products/substance-adverseevent/adverseeventcvm/substance-adverseeventcvm.component.ts +++ b/src/app/fda/substance-details/substance-products/substance-adverseevent/adverseeventcvm/substance-adverseeventcvm.component.ts @@ -37,6 +37,7 @@ export class SubstanceAdverseEventCvmComponent extends SubstanceDetailsBaseTable loadingStatus = ''; public sortValues = adverseEventCvmSearchSortValues; private subscriptions: Array = []; + canExport: boolean = false; displayedColumns: string[] = [ 'adverseEvent', 'species', 'adverseEventCount', 'routeOfAdmin' @@ -53,11 +54,8 @@ export class SubstanceAdverseEventCvmComponent extends SubstanceDetailsBaseTable super(gaService, adverseEventService); } - ngOnInit() { - const rolesSubscription = this.authService.hasAnyRolesAsync('Admin', 'Updater', 'SuperUpdater').subscribe(response => { - this.isAdmin = response; - }); - this.subscriptions.push(rolesSubscription); + async ngOnInit() { + this.canExport = await this.authService.hasSpecificPrivilege("Export Data"); if (this.bdnum) { this.getAdverseEventCvm(); // this.getSubstanceAdverseEventCvm(); diff --git a/src/app/fda/substance-details/substance-products/substance-adverseevent/adverseeventdme/substance-adverseeventdme.component.html b/src/app/fda/substance-details/substance-products/substance-adverseevent/adverseeventdme/substance-adverseeventdme.component.html index f0f8bb2c2..be37a6a10 100644 --- a/src/app/fda/substance-details/substance-products/substance-adverseevent/adverseeventdme/substance-adverseeventdme.component.html +++ b/src/app/fda/substance-details/substance-products/substance-adverseevent/adverseeventdme/substance-adverseeventdme.component.html @@ -1,29 +1,14 @@
    Adverse Event DME     - - +
    -
    diff --git a/src/app/fda/substance-details/substance-products/substance-adverseevent/adverseeventdme/substance-adverseeventdme.component.ts b/src/app/fda/substance-details/substance-products/substance-adverseevent/adverseeventdme/substance-adverseeventdme.component.ts index c176ba898..3f33d8464 100644 --- a/src/app/fda/substance-details/substance-products/substance-adverseevent/adverseeventdme/substance-adverseeventdme.component.ts +++ b/src/app/fda/substance-details/substance-products/substance-adverseevent/adverseeventdme/substance-adverseeventdme.component.ts @@ -37,6 +37,7 @@ export class SubstanceAdverseEventDmeComponent extends SubstanceDetailsBaseTable loadingStatus = ''; public sortValues = adverseEventDmeSearchSortValues; private subscriptions: Array = []; + canExport: boolean = false; displayedColumns: string[] = [ 'dmeReactions', 'ptTermMeddra', 'caseCount', 'dmeCount', 'dmeCountPercent', 'weightedAvgPrr' @@ -53,12 +54,8 @@ export class SubstanceAdverseEventDmeComponent extends SubstanceDetailsBaseTable super(gaService, adverseEventService); } - ngOnInit() { - const rolesSubscription = this.authService.hasAnyRolesAsync('Admin', 'Updater', 'SuperUpdater').subscribe(response => { - this.isAdmin = response; - }); - this.subscriptions.push(rolesSubscription); - + async ngOnInit() { + this.canExport = await this.authService.hasSpecificPrivilege('Export Data'); if (this.bdnum) { this.getAdverseEventDme(); // this.getSubstanceAdverseEventDme(); diff --git a/src/app/fda/substance-details/substance-products/substance-adverseevent/adverseeventpt/substance-adverseeventpt.component.html b/src/app/fda/substance-details/substance-products/substance-adverseevent/adverseeventpt/substance-adverseeventpt.component.html index ce54b80cc..90fb0f3a0 100644 --- a/src/app/fda/substance-details/substance-products/substance-adverseevent/adverseeventpt/substance-adverseeventpt.component.html +++ b/src/app/fda/substance-details/substance-products/substance-adverseevent/adverseeventpt/substance-adverseeventpt.component.html @@ -5,15 +5,8 @@ Adverse Event PT
    - - + - -
    @@ -45,7 +37,7 @@ {{application.appType}}    - diff --git a/src/app/fda/substance-details/substance-products/substance-application/substance-application.component.ts b/src/app/fda/substance-details/substance-products/substance-application/substance-application.component.ts index d8632c2b9..a4dafda74 100644 --- a/src/app/fda/substance-details/substance-products/substance-application/substance-application.component.ts +++ b/src/app/fda/substance-details/substance-products/substance-application/substance-application.component.ts @@ -54,6 +54,8 @@ export class SubstanceApplicationComponent extends SubstanceDetailsBaseTableDisp 'appStatus', 'applicationSubType' ]; + canExport: boolean = false; + canUpdate: boolean = false; constructor( private router: Router, @@ -67,10 +69,9 @@ export class SubstanceApplicationComponent extends SubstanceDetailsBaseTableDisp super(gaService, applicationService); } - ngOnInit() { - this.authService.hasAnyRolesAsync('Admin', 'Updater', 'SuperUpdater').pipe(take(1)).subscribe(response => { - this.isAdmin = response; - }); + async ngOnInit() { + this.canExport = await this.authService.hasSpecificPrivilege('Export Data'); + this.canUpdate = await this.authService.hasSpecificPrivilege('Edit') if (this.bdnum) { this.getApplicationCenterList(); diff --git a/src/app/fda/substance-details/substance-products/substance-products.component.html b/src/app/fda/substance-details/substance-products/substance-products.component.html index 932aa0391..b02a349e7 100644 --- a/src/app/fda/substance-details/substance-products/substance-products.component.html +++ b/src/app/fda/substance-details/substance-products/substance-products.component.html @@ -26,7 +26,7 @@     - + diff --git a/src/app/core/substance-ssg2/substance-ssg2-form.component.ts b/src/app/core/substance-ssg2/substance-ssg2-form.component.ts index 296440d4e..de9bbe988 100644 --- a/src/app/core/substance-ssg2/substance-ssg2-form.component.ts +++ b/src/app/core/substance-ssg2/substance-ssg2-form.component.ts @@ -71,8 +71,6 @@ export class SubstanceSsg2FormComponent implements OnInit, AfterViewInit, OnDest definition: SubstanceFormDefinition; user: string; feature: string; - isAdmin: boolean; - isUpdater: boolean; messageField: string; uuid: string; substanceClass: string; @@ -282,8 +280,6 @@ export class SubstanceSsg2FormComponent implements OnInit, AfterViewInit, OnDest if (this.configService.configData && this.configService.configData.autoSaveWait) { this.autoSaveWait = this.configService.configData.autoSaveWait; } - this.isAdmin = this.authService.hasRoles('admin'); - this.isUpdater = this.authService.hasAnyRoles('Updater', 'SuperUpdater'); this.overlayContainer = this.overlayContainerService.getContainerElement(); this.imported = false; const routeSubscription = this.activatedRoute diff --git a/src/app/core/substance-ssg4m/substance-ssg4m-form.component.html b/src/app/core/substance-ssg4m/substance-ssg4m-form.component.html index 0560b73ad..ebcf455b8 100644 --- a/src/app/core/substance-ssg4m/substance-ssg4m-form.component.html +++ b/src/app/core/substance-ssg4m/substance-ssg4m-form.component.html @@ -29,21 +29,6 @@ - - -
    @@ -101,11 +80,6 @@
    -
    diff --git a/src/app/core/substance-ssg4m/substance-ssg4m-form.component.ts b/src/app/core/substance-ssg4m/substance-ssg4m-form.component.ts index d09ded630..092b2ab0e 100644 --- a/src/app/core/substance-ssg4m/substance-ssg4m-form.component.ts +++ b/src/app/core/substance-ssg4m/substance-ssg4m-form.component.ts @@ -71,8 +71,6 @@ export class SubstanceSsg4ManufactureFormComponent implements OnInit, AfterViewI definition: SubstanceFormDefinition; user: string; feature: string; - isAdmin: boolean; - isUpdater: boolean; messageField: string; errorMessage: string; microserviceStatusUp = false; @@ -139,8 +137,6 @@ export class SubstanceSsg4ManufactureFormComponent implements OnInit, AfterViewI this.showHeaderBar = this.activatedRoute.snapshot.queryParams['header'] || 'true'; this.showFormReadOnly = this.activatedRoute.snapshot.queryParams['readonly'] || 'false'; this.loadingService.setLoading(true); - this.isAdmin = this.authService.hasRoles('admin'); - this.isUpdater = this.authService.hasAnyRoles('Updater', 'SuperUpdater'); this.overlayContainer = this.overlayContainerService.getContainerElement(); this.imported = false; diff --git a/src/app/core/substances-browse/substances-browse.component.ts b/src/app/core/substances-browse/substances-browse.component.ts index 22e23bc35..4c0d77ee3 100644 --- a/src/app/core/substances-browse/substances-browse.component.ts +++ b/src/app/core/substances-browse/substances-browse.component.ts @@ -101,7 +101,6 @@ export class SubstancesBrowseComponent implements OnInit, AfterViewInit, OnDestr showAudit: boolean; private overlayContainer: HTMLElement; private subscriptions: Array = []; - isAdmin = false; canUpdate = false; isLoggedIn = false; showExactMatches = false; diff --git a/src/app/fda/adverse-event/adverse-events-dme-browse/adverse-events-dme-browse.component.html b/src/app/fda/adverse-event/adverse-events-dme-browse/adverse-events-dme-browse.component.html index 9179dd8b4..0a1ec7b43 100644 --- a/src/app/fda/adverse-event/adverse-events-dme-browse/adverse-events-dme-browse.component.html +++ b/src/app/fda/adverse-event/adverse-events-dme-browse/adverse-events-dme-browse.component.html @@ -128,7 +128,7 @@ --> -
    +
    @@ -32,7 +32,7 @@

    Substances in Clinical Trial +
    @@ -51,10 +51,10 @@

    Substances in Clinical Trial Name - + {{element.name}} - + @@ -63,10 +63,10 @@

    Substances in Clinical Trial Substance Key - + {{element.substanceKey}} - + @@ -77,11 +77,11 @@

    Substances in Clinical Trial Protected Match - + {{element.protectedMatch}} - + @@ -91,7 +91,7 @@

    Substances in Clinical Trial Roles - @@ -137,17 +137,17 @@

    Outcome Result Notes

    -
     
    Note: {{i+1}}
    -
    +
    - +
     
    @@ -248,7 +248,7 @@

    Outcome Result Notes

    Go to browse

    -
    isAdmin: {{isAdmin}}
    +
    canUpdate: {{canUpdate}}
     
    diff --git a/src/app/fda/clinical-trials/clinical-trial-edit/clinical-trial-edit.component.ts b/src/app/fda/clinical-trials/clinical-trial-edit/clinical-trial-edit.component.ts index c779c6018..2ba1ea953 100644 --- a/src/app/fda/clinical-trials/clinical-trial-edit/clinical-trial-edit.component.ts +++ b/src/app/fda/clinical-trials/clinical-trial-edit/clinical-trial-edit.component.ts @@ -34,8 +34,8 @@ export class ClinicalTrialEditComponent implements OnInit, AfterViewInit, OnDest defaultSubstanceKeyType = 'UUID'; agencySubstanceKeyType = 'UUID'; - canEdit: boolean = false; - isTesting = false; + canUpdate: boolean = false; + isTesting = true; displayedColumns: string[]; dataSource = new MatTableDataSource([]); public _trialNumber: string; @@ -67,8 +67,8 @@ export class ClinicalTrialEditComponent implements OnInit, AfterViewInit, OnDest } async ngOnInit() { - this.canEdit = await this.authService.hasSpecificPrivilege('Edit'); - if (this.canEdit) { + this.canUpdate = await this.authService.hasSpecificPrivilege('Edit'); + if (this.canUpdate) { this.displayedColumns = ['id', 'name', 'substanceKey', 'protectedMatch', 'substanceRoles', 'orgSubstanceKey', 'link', 'delete']; } else { this.displayedColumns = ['name', 'substanceKey', 'protectedMatch', 'substanceRoles', 'orgSubstanceKey', 'link']; diff --git a/src/app/fda/clinical-trials/clinical-trials-browse/clinical-trials-browse.component.ts b/src/app/fda/clinical-trials/clinical-trials-browse/clinical-trials-browse.component.ts index 25f15a1a6..a5f70af3b 100644 --- a/src/app/fda/clinical-trials/clinical-trials-browse/clinical-trials-browse.component.ts +++ b/src/app/fda/clinical-trials/clinical-trials-browse/clinical-trials-browse.component.ts @@ -54,7 +54,6 @@ export class ClinicalTrialsBrowseComponent implements OnInit, AfterViewInit, OnD public smiles: string; private argsHash?: number; public auth?: Auth; - showAudit: boolean; public order: string; // public sortValues = searchSortValues; searchText: string[] = []; @@ -62,7 +61,7 @@ export class ClinicalTrialsBrowseComponent implements OnInit, AfterViewInit, OnD toggle: Array = []; private subscriptions: Array = []; dataSource = new MatTableDataSource([]); - isAdmin: boolean; + canDelete: boolean = false; showExactMatches = false; private isComponentInit = false; @@ -92,7 +91,7 @@ export class ClinicalTrialsBrowseComponent implements OnInit, AfterViewInit, OnD private titleService: Title ) {} - ngOnInit() { + async ngOnInit() { this.facetManagerService.registerGetFacetsHandler(this.clinicalTrialService.getClinicalTrialsFacets); this.pageSize = 10; this.pageIndex = 0; @@ -113,16 +112,15 @@ export class ClinicalTrialsBrowseComponent implements OnInit, AfterViewInit, OnD } this.overlayContainer = this.overlayContainerService.getContainerElement(); + this.canDelete = await this.authService.hasSpecificPrivilege('Delete Lower Level Items'); const authSubscription = this.authService.getAuth().subscribe(auth => { - this.isAdmin = this.authService.hasAnyRoles('Updater', 'SuperUpdater'); + // testing - // this.isAdmin = true; - // this.showAudit = this.authService.hasRoles('admin'); - if (this.isAdmin) { - this.displayedColumns = ['edit', 'trialNumber', 'title', 'lastUpdated', 'delete']; - } else { + if (this.canDelete) { + this.displayedColumns = ['edit', 'trialNumber', 'title', 'lastUpdated', 'delete']; + } else { this.displayedColumns = ['edit', 'trialNumber', 'title', 'lastUpdated']; - } + } }); this.searchTypes = [ {'title': 'All', 'value': 'all'}, diff --git a/src/app/fda/config/config.json b/src/app/fda/config/config.json index 78dc11407..0899d50c4 100644 --- a/src/app/fda/config/config.json +++ b/src/app/fda/config/config.json @@ -650,7 +650,7 @@ "filters": [ { "filterName": "hasCredentials", - "propertyToCheck": "admin" + "propertyToCheck": "Restore Previous Versions" } ] } diff --git a/src/app/fda/invitro-pharmacology/invitro-pharmacology-browse/invitro-pharmacology-browse.component.html b/src/app/fda/invitro-pharmacology/invitro-pharmacology-browse/invitro-pharmacology-browse.component.html index e972cdc5c..be47555cb 100644 --- a/src/app/fda/invitro-pharmacology/invitro-pharmacology-browse/invitro-pharmacology-browse.component.html +++ b/src/app/fda/invitro-pharmacology/invitro-pharmacology-browse/invitro-pharmacology-browse.component.html @@ -116,18 +116,6 @@ - @@ -140,18 +128,8 @@ - - - - @@ -1094,203 +966,12 @@ - -
    @@ -1330,23 +1011,6 @@

    - - @@ -1864,7 +1508,7 @@
    NumberAssay Found In Database External Assay Source * External Assay ID * Assay ID
    {{(indexControl+1)}} - -
    YES
    -
    - -
    NO - -
    -
    - - - Register Assay - -
    - -
    ERROR
    -
    -
    {{ctrl.externalAssaySource}} @@ -551,7 +457,6 @@ {{result.externalAssayUrl}} {{result.assayId}} {{result.testDate}} {{result.testAgentConcentration}} {{assayscreen.studyType}} Test Agent Concentration @@ -1508,7 +1172,7 @@ @@ -1641,20 +1305,6 @@
    -
    @@ -1699,17 +1349,11 @@ alt="View Assay Screening Record" matTooltip='View Assay Record'>View     - -
    -
    +
    diff --git a/src/app/fda/invitro-pharmacology/invitro-pharmacology-browse/invitro-pharmacology-browse.component.ts b/src/app/fda/invitro-pharmacology/invitro-pharmacology-browse/invitro-pharmacology-browse.component.ts index ef550c2c3..65448f0c1 100644 --- a/src/app/fda/invitro-pharmacology/invitro-pharmacology-browse/invitro-pharmacology-browse.component.ts +++ b/src/app/fda/invitro-pharmacology/invitro-pharmacology-browse/invitro-pharmacology-browse.component.ts @@ -79,7 +79,9 @@ export class InvitroPharmacologyBrowseComponent implements OnInit { skip: number; isLoading = true; isError = false; - isAdmin: boolean; + canExport: boolean = false; + canUpdate: boolean = false; + canSaveJson: boolean = false; isLoggedIn = false; dataSource = []; appType: string; @@ -196,7 +198,7 @@ export class InvitroPharmacologyBrowseComponent implements OnInit { private dialog: MatDialog ) { } - ngOnInit(): void { + async ngOnInit() { this.facetManagerService.registerGetFacetsHandler(this.invitroPharmacologyService.getInvitroPharmacologyFacets); this.titleService.setTitle(`In Vitro Pharmacology Browser`); @@ -212,7 +214,6 @@ export class InvitroPharmacologyBrowseComponent implements OnInit { if (auth) { this.isLoggedIn = true; } - this.isAdmin = this.authService.hasAnyRoles('Admin', 'Updater', 'SuperUpdater'); }); this.subscriptions.push(authSubscription); @@ -233,7 +234,9 @@ export class InvitroPharmacologyBrowseComponent implements OnInit { this.searchValue = params.get('search'); }); this.subscriptions.push(paramsSubscription); - + this.canExport = await this.authService.hasSpecificPrivilege('Export Data'); + this.canUpdate = await this.authService.hasSpecificPrivilege('Edit'); + this.canSaveJson = await this.authService.hasSpecificPrivilege('Save Record JSON'); } ngAfterViewInit() { diff --git a/src/app/fda/invitro-pharmacology/invitro-pharmacology-details/invitro-pharmacology-details-testagent/invitro-pharmacology-details-testagent.component.html b/src/app/fda/invitro-pharmacology/invitro-pharmacology-details/invitro-pharmacology-details-testagent/invitro-pharmacology-details-testagent.component.html index 98e1416f4..6b6e1de58 100644 --- a/src/app/fda/invitro-pharmacology/invitro-pharmacology-details/invitro-pharmacology-details-testagent/invitro-pharmacology-details-testagent.component.html +++ b/src/app/fda/invitro-pharmacology/invitro-pharmacology-details/invitro-pharmacology-details-testagent/invitro-pharmacology-details-testagent.component.html @@ -48,7 +48,7 @@
    -   @@ -285,7 +285,7 @@
    - Edit Summary Record @@ -316,14 +316,6 @@ alt="View Assay Record" matTooltip='View Assay Screening Record'>View - -
    diff --git a/src/app/fda/invitro-pharmacology/invitro-pharmacology-details/invitro-pharmacology-details-testagent/invitro-pharmacology-details-testagent.component.ts b/src/app/fda/invitro-pharmacology/invitro-pharmacology-details/invitro-pharmacology-details-testagent/invitro-pharmacology-details-testagent.component.ts index 9ef221a9a..871e07c3a 100644 --- a/src/app/fda/invitro-pharmacology/invitro-pharmacology-details/invitro-pharmacology-details-testagent/invitro-pharmacology-details-testagent.component.ts +++ b/src/app/fda/invitro-pharmacology/invitro-pharmacology-details/invitro-pharmacology-details-testagent/invitro-pharmacology-details-testagent.component.ts @@ -94,7 +94,7 @@ export class InvitroPharmacologyDetailsTestagentComponent implements OnInit { isLoading = true; isError = false; - isAdmin: boolean; + canUpdate: boolean; isLoggedIn = false; dataSource = []; hasBackdrop = false; @@ -178,13 +178,12 @@ export class InvitroPharmacologyDetailsTestagentComponent implements OnInit { public assays: Array; - ngOnInit(): void { + async ngOnInit() { // Check Login const authSubscription = this.authService.getAuth().subscribe(auth => { if (auth) { this.isLoggedIn = true; } - this.isAdmin = this.authService.hasAnyRoles('Admin', 'Updater', 'SuperUpdater'); }); this.subscriptions.push(authSubscription); @@ -197,6 +196,7 @@ export class InvitroPharmacologyDetailsTestagentComponent implements OnInit { } else { this.getAllAssays(); } + this.canUpdate = await this.authService.hasSpecificPrivilege('Edit'); } getAllAssays(): void { diff --git a/src/app/fda/invitro-pharmacology/invitro-pharmacology-details/invitro-pharmacology-details.component.html b/src/app/fda/invitro-pharmacology/invitro-pharmacology-details/invitro-pharmacology-details.component.html index e2acb53d4..9c846f51b 100644 --- a/src/app/fda/invitro-pharmacology/invitro-pharmacology-details/invitro-pharmacology-details.component.html +++ b/src/app/fda/invitro-pharmacology/invitro-pharmacology-details/invitro-pharmacology-details.component.html @@ -11,7 +11,7 @@
    -    @@ -164,16 +164,6 @@ matTooltip="Go to Substance Details page"> {{assay.targetName}} - -
    @@ -372,7 +362,7 @@ diff --git a/src/app/fda/invitro-pharmacology/invitro-pharmacology-details/invitro-pharmacology-details.component.ts b/src/app/fda/invitro-pharmacology/invitro-pharmacology-details/invitro-pharmacology-details.component.ts index b41dbcf8d..ac36a1566 100644 --- a/src/app/fda/invitro-pharmacology/invitro-pharmacology-details/invitro-pharmacology-details.component.ts +++ b/src/app/fda/invitro-pharmacology/invitro-pharmacology-details/invitro-pharmacology-details.component.ts @@ -44,7 +44,7 @@ export class InvitroPharmacologyDetailsComponent implements OnInit, OnDestroy { jsonFileName: string; flagIconSrcPath: string; - canEdit: boolean = false; + canUpdate: boolean = false; private overlayContainer: HTMLElement; private subscriptions: Array = []; @@ -67,14 +67,13 @@ export class InvitroPharmacologyDetailsComponent implements OnInit, OnDestroy { this.overlayContainer = this.overlayContainerService.getContainerElement(); this.loadingService.setLoading(true); - this.canEdit = await this.authService.hasSpecificPrivilege('Edit'); + this.canUpdate = await this.authService.hasSpecificPrivilege('Edit'); this.id = this.activatedRoute.snapshot.params['id']; if (this.id != null) { this.getInvitroPharmacology(); } else { this.handleSubstanceRetrivalError(); } - //this.loadingService.setLoading(false); } ngOnDestroy(): void { diff --git a/src/app/fda/invitro-pharmacology/invitro-pharmacology-form/invitro-pharmacology-form.component.html b/src/app/fda/invitro-pharmacology/invitro-pharmacology-form/invitro-pharmacology-form.component.html index 9f55ce6d6..b8829191e 100644 --- a/src/app/fda/invitro-pharmacology/invitro-pharmacology-form/invitro-pharmacology-form.component.html +++ b/src/app/fda/invitro-pharmacology/invitro-pharmacology-form/invitro-pharmacology-form.component.html @@ -106,18 +106,6 @@   
    - -
    @@ -308,9 +296,6 @@
    -
    Laboratory Name: * @@ -527,9 +512,6 @@ -
    Sponsor Contact Name: * @@ -607,18 +589,6 @@
    - -
    @@ -684,10 +654,6 @@ - -
    @@ -895,20 +861,6 @@
    - -
    @@ -1075,13 +1027,6 @@ - -
    diff --git a/src/app/fda/invitro-pharmacology/invitro-pharmacology-form/invitro-pharmacology-form.component.ts b/src/app/fda/invitro-pharmacology/invitro-pharmacology-form/invitro-pharmacology-form.component.ts index 38eaeae9f..936cd2510 100644 --- a/src/app/fda/invitro-pharmacology/invitro-pharmacology-form/invitro-pharmacology-form.component.ts +++ b/src/app/fda/invitro-pharmacology/invitro-pharmacology-form/invitro-pharmacology-form.component.ts @@ -131,7 +131,6 @@ export class InvitroPharmacologyFormComponent implements OnInit, OnDestroy { downloadJsonHref: any; jsonFileName: string; - isAdmin = false; isLoading = true; private overlayContainer: HTMLElement; private subscriptions: Array = []; @@ -164,7 +163,6 @@ export class InvitroPharmacologyFormComponent implements OnInit, OnDestroy { this.loadingService.setLoading(this.isLoading); // Get Username and Admin details - this.isAdmin = this.authService.hasRoles('admin'); this.username = this.authService.getUser(); // Get Invitro Pharmacology Substance Key Type from the configuration file diff --git a/src/app/fda/invitro-pharmacology/invitro-pharmacology-form/invitro-pharmacology-summary-form/invitro-pharmacology-summary-form.component.html b/src/app/fda/invitro-pharmacology/invitro-pharmacology-form/invitro-pharmacology-summary-form/invitro-pharmacology-summary-form.component.html index ade0454f5..331ab5ae3 100644 --- a/src/app/fda/invitro-pharmacology/invitro-pharmacology-form/invitro-pharmacology-summary-form/invitro-pharmacology-summary-form.component.html +++ b/src/app/fda/invitro-pharmacology/invitro-pharmacology-form/invitro-pharmacology-summary-form/invitro-pharmacology-summary-form.component.html @@ -9,12 +9,6 @@ Export JSON     - - - - -   @@ -93,14 +78,6 @@ {{title}}    - - - diff --git a/src/app/fda/invitro-pharmacology/invitro-pharmacology-form/invitro-pharmacology-summary-form/invitro-pharmacology-summary-form.component.ts b/src/app/fda/invitro-pharmacology/invitro-pharmacology-form/invitro-pharmacology-summary-form/invitro-pharmacology-summary-form.component.ts index e9fdd42ed..850175e92 100644 --- a/src/app/fda/invitro-pharmacology/invitro-pharmacology-form/invitro-pharmacology-summary-form/invitro-pharmacology-summary-form.component.ts +++ b/src/app/fda/invitro-pharmacology/invitro-pharmacology-form/invitro-pharmacology-summary-form/invitro-pharmacology-summary-form.component.ts @@ -114,7 +114,6 @@ export class InvitroPharmacologySummaryFormComponent implements OnInit, OnDestro downloadJsonHref: any; jsonFileName: string; - isAdmin = false; isLoading = true; private overlayContainer: HTMLElement; private subscriptions: Array = []; @@ -139,7 +138,6 @@ export class InvitroPharmacologySummaryFormComponent implements OnInit, OnDestro ngOnInit() { // Get Username and Admin details - this.isAdmin = this.authService.hasRoles('admin'); this.username = this.authService.getUser(); this.loadingService.setLoading(true); diff --git a/src/app/fda/invitro-pharmacology/invitro-pharmacology.component.ts b/src/app/fda/invitro-pharmacology/invitro-pharmacology.component.ts index 865d89ae5..22a3ac4de 100644 --- a/src/app/fda/invitro-pharmacology/invitro-pharmacology.component.ts +++ b/src/app/fda/invitro-pharmacology/invitro-pharmacology.component.ts @@ -32,8 +32,7 @@ export class InvitroPharmacologyComponent implements OnInit { submitDateMessage = ''; statusDateMessage = ''; // appForm: FormGroup; - isAdmin = false; - + constructor() { } ngOnInit(): void { diff --git a/src/app/fda/product/product-form/product-form.component.html b/src/app/fda/product/product-form/product-form.component.html index 0f56f5621..f8bff9fd4 100644 --- a/src/app/fda/product/product-form/product-form.component.html +++ b/src/app/fda/product/product-form/product-form.component.html @@ -41,7 +41,7 @@     - + diff --git a/src/app/fda/substance-details/substance-products/substance-clinical-trials-eu/substance-clinical-trials-eu.component.ts b/src/app/fda/substance-details/substance-products/substance-clinical-trials-eu/substance-clinical-trials-eu.component.ts index b81ce98a0..84bdec0e4 100644 --- a/src/app/fda/substance-details/substance-products/substance-clinical-trials-eu/substance-clinical-trials-eu.component.ts +++ b/src/app/fda/substance-details/substance-products/substance-clinical-trials-eu/substance-clinical-trials-eu.component.ts @@ -49,6 +49,8 @@ export class SubstanceClinicalTrialsEuropeComponent extends SubstanceDetailsBase 'conditionsEU' ]; + canExport: boolean = false; + constructor( public gaService: GoogleAnalyticsService, private clinicalTrialService: ClinicalTrialService, @@ -61,11 +63,9 @@ export class SubstanceClinicalTrialsEuropeComponent extends SubstanceDetailsBase super(gaService, clinicalTrialService); } - ngOnInit() { + async ngOnInit() { this.loadedComponents = this.configService.configData.loadedComponents || null; - this.authService.hasAnyRolesAsync('Admin', 'Updater', 'SuperUpdater').pipe(take(1)).subscribe(response => { - this.isAdmin = response; - }); + this.canExport = await this.authService.hasSpecificPrivilege('Export Data'); if (this.substanceUuid) { this.getSubstanceClinicalTrialsEurope(null, 'initial'); } diff --git a/src/app/fda/substance-details/substance-products/substance-clinical-trials/substance-clinical-trials.component.html b/src/app/fda/substance-details/substance-products/substance-clinical-trials/substance-clinical-trials.component.html index c5a1e7671..6bed7145f 100644 --- a/src/app/fda/substance-details/substance-products/substance-clinical-trials/substance-clinical-trials.component.html +++ b/src/app/fda/substance-details/substance-products/substance-clinical-trials/substance-clinical-trials.component.html @@ -9,7 +9,7 @@
    - + diff --git a/src/app/fda/substance-details/substance-products/substance-clinical-trials/substance-clinical-trials.component.ts b/src/app/fda/substance-details/substance-products/substance-clinical-trials/substance-clinical-trials.component.ts index 32ad6a8ff..615761efe 100644 --- a/src/app/fda/substance-details/substance-products/substance-clinical-trials/substance-clinical-trials.component.ts +++ b/src/app/fda/substance-details/substance-products/substance-clinical-trials/substance-clinical-trials.component.ts @@ -51,6 +51,8 @@ export class SubstanceClinicalTrialsComponent extends SubstanceDetailsBaseTableD 'outcomemeasures' ]; + canExport: boolean = false; + constructor( public gaService: GoogleAnalyticsService, private clinicalTrialService: ClinicalTrialService, @@ -63,11 +65,9 @@ export class SubstanceClinicalTrialsComponent extends SubstanceDetailsBaseTableD super(gaService, clinicalTrialService); } - ngOnInit() { + async ngOnInit() { this.loadedComponents = this.configService.configData.loadedComponents || null; - this.authService.hasAnyRolesAsync('Admin', 'Updater', 'SuperUpdater').pipe(take(1)).subscribe(response => { - this.isAdmin = response; - }); + this.canExport = await this.authService.hasSpecificPrivilege('Export Data'); if (this.substanceUuid) { this.getSubstanceClinicalTrials(null, 'initial'); } diff --git a/src/app/fda/substance-details/substance-products/substance-details-base-table-display.ts b/src/app/fda/substance-details/substance-products/substance-details-base-table-display.ts index 426c756b8..9c7780fd1 100644 --- a/src/app/fda/substance-details/substance-products/substance-details-base-table-display.ts +++ b/src/app/fda/substance-details/substance-products/substance-details-base-table-display.ts @@ -9,7 +9,6 @@ export class SubstanceDetailsBaseTableDisplay extends SubstanceCardBaseFilteredL totalRecords: 0; public results: Array = []; - isAdmin = false; exportUrl: string; @Input() bdnum: string; diff --git a/src/app/fda/substance-details/substance-products/substance-impurities/substance-impurities.component.html b/src/app/fda/substance-details/substance-products/substance-impurities/substance-impurities.component.html index 14aa8b064..5ed63f773 100644 --- a/src/app/fda/substance-details/substance-products/substance-impurities/substance-impurities.component.html +++ b/src/app/fda/substance-details/substance-products/substance-impurities/substance-impurities.component.html @@ -3,7 +3,7 @@     - + diff --git a/src/app/core/admin/import-management/import-management.component.ts b/src/app/core/admin/import-management/import-management.component.ts index 634fb0076..72926ce8c 100644 --- a/src/app/core/admin/import-management/import-management.component.ts +++ b/src/app/core/admin/import-management/import-management.component.ts @@ -1,4 +1,4 @@ -import { Component, OnInit } from '@angular/core'; +import { AfterViewInit, Component, OnInit } from '@angular/core'; import { FormControl, FormGroup, Validators, FormBuilder } from '@angular/forms'; import { AdminService } from '@gsrs-core/admin/admin.service'; import { take } from 'rxjs/operators'; @@ -192,7 +192,6 @@ ngOnInit() { }); - } close(param?: string) { diff --git a/src/app/core/admin/user-management/user-edit-dialog/user-edit-dialog.component.ts b/src/app/core/admin/user-management/user-edit-dialog/user-edit-dialog.component.ts index 3ce03ad93..215a2dec4 100644 --- a/src/app/core/admin/user-management/user-edit-dialog/user-edit-dialog.component.ts +++ b/src/app/core/admin/user-management/user-edit-dialog/user-edit-dialog.component.ts @@ -6,6 +6,7 @@ import { IfStmt } from '@angular/compiler'; import { AuthService, Auth } from '@gsrs-core/auth'; import { take } from 'rxjs/operators'; import { UserEditObject } from '@gsrs-core/admin/admin-objects.model'; +import { Router } from '@angular/router'; @Component({ selector: 'app-user-edit-dialog', @@ -44,6 +45,7 @@ export class UserEditDialogComponent implements OnInit { private adminService: AdminService, public dialogRef: MatDialogRef, private authService: AuthService, + private router: Router, @Inject(MAT_DIALOG_DATA) public data: any ) { console.log(`in UserEditDialogComponent ctor, data: ${JSON.stringify(data)}`); @@ -56,12 +58,15 @@ export class UserEditDialogComponent implements OnInit { ngOnInit() { if (this.user) { console.log(`UserEditDialogComponent ngOnInit have user`); + if(! this.authService.hasSpecificPrivilege('Manage Users')) { + alert("Sorry! Unable to verify that you have the privileges to access this page"); + this.router.parseUrl('/home'); + } this.checkRoles(); this.originalName = this.user.username; this.loading = false; this.newUser = false; this.userHasAdminRole = this.checkIfUserHasAdminRole(this.user.roles); - this.authService.hasSpecificPrivilege('Manage Users') this.adminService.getGroups().pipe(take(1)).subscribe( response => { this.setupAssignableRoles(); this.groups = []; diff --git a/src/app/core/app-routing.module.ts b/src/app/core/app-routing.module.ts index 1f553045e..88201f9b1 100644 --- a/src/app/core/app-routing.module.ts +++ b/src/app/core/app-routing.module.ts @@ -24,6 +24,7 @@ import { UnauthorizedComponent } from '@gsrs-core/unauthorized/unauthorized.comp import { SubstanceSsg4ManufactureFormComponent } from './substance-ssg4m/substance-ssg4m-form.component'; import { ImportBrowseComponent } from '@gsrs-core/admin/import-browse/import-browse.component'; import { CanImportData } from './admin/can-import-data'; +import { ImportManagementComponent } from './admin/import-management/import-management.component'; const childRoutes: Routes = [ { @@ -91,26 +92,28 @@ const childRoutes: Routes = [ canActivate: [CanActivateSubstanceForm], canDeactivate: [CanDeactivateSubstanceFormGuard] }, - { - path: 'admin', - component: AdminComponent, - canActivate: [CanActivateAdmin], - - }, - { path: 'admin/staging-area', component: ImportBrowseComponent, pathMatch: 'full', canActivate: [CanImportData] - }, { path: 'admin/:function', component: AdminComponent, canActivate: [CanActivateAdmin], - - + }, + { + path: 'admin', + component: AdminComponent, + canActivate: [CanActivateAdmin], + children :[ + { + path: 'import', + component: ImportManagementComponent, + canActivate: [CanImportData] + } + ] }, { path: 'monitor/:id', diff --git a/src/app/core/auth/auth.service.ts b/src/app/core/auth/auth.service.ts index 2b0a1d3f4..4e0e704ab 100644 --- a/src/app/core/auth/auth.service.ts +++ b/src/app/core/auth/auth.service.ts @@ -259,6 +259,12 @@ get auth(): Auth { return this._privileges != null && this._privileges.some(p=>p.privilege=="Edit"); } + async hasAnyPrivilege(...privs) : Promise< boolean> { + if( this._privileges == null || this._privileges.length === 0) { + const privs = await firstValueFrom(this.fetchPrivs()); + } + return privs.some(p=>this._privileges.some(pp=>pp.privilege.toUpperCase()== p.toUpperCase())); + } async hasSpecificPrivilege(requestedPrivilege: string ):Promise { if( this._privileges == null || this._privileges.length === 0) { diff --git a/src/app/core/base/base.component.html b/src/app/core/base/base.component.html index d556b5c36..eafdaff13 100644 --- a/src/app/core/base/base.component.html +++ b/src/app/core/base/base.component.html @@ -192,13 +192,13 @@ Saved Edit Drafts - + Import Data - + Legacy Data Import - + CV Management diff --git a/src/app/core/base/base.component.ts b/src/app/core/base/base.component.ts index e67d5f96f..2d6b38c49 100644 --- a/src/app/core/base/base.component.ts +++ b/src/app/core/base/base.component.ts @@ -40,7 +40,7 @@ export class BaseComponent implements OnInit, OnDestroy { classicLinkPath: string; classicLinkQueryParamsString: string; canConfigureSystem: boolean = false; - canImportData: boolean = false; + canUserImportData: boolean = false; canManageCVs: boolean = false; contactEmail: string; version?: string; @@ -153,8 +153,8 @@ export class BaseComponent implements OnInit, OnDestroy { } this.canConfigureSystem = await this.authService.hasSpecificPrivilege('Configure System'); console.log(`canConfigureSystem ${this.canConfigureSystem}`); - this.canImportData = await this.authService.hasSpecificPrivilege('Import Data'); - console.log(`this.canImportData: ${this.canImportData}`); + this.canUserImportData = await this.authService.hasSpecificPrivilege('Import Data'); + console.log(`this.canUserImportData: ${this.canUserImportData}`); this.canRegister=await this.authService.canEditData(); console.log(`in BaseComponent, canRegister: ${this.canRegister}`); this.canManageCVs = await this.authService.hasSpecificPrivilege("Manage CVs"); From 2e3ae9ecb895f661e51b4aac80a44f194ca29e24 Mon Sep 17 00:00:00 2001 From: Mitch Miller Date: Fri, 24 Oct 2025 15:51:21 -0400 Subject: [PATCH 134/408] on admin page, verify user privs before navigating to tabs --- src/app/core/admin/admin.component.ts | 100 ++++++++++++++---- .../cv-term-dialog.component.ts | 16 +-- 2 files changed, 84 insertions(+), 32 deletions(-) diff --git a/src/app/core/admin/admin.component.ts b/src/app/core/admin/admin.component.ts index 239593c13..ea71e39aa 100644 --- a/src/app/core/admin/admin.component.ts +++ b/src/app/core/admin/admin.component.ts @@ -2,6 +2,7 @@ import { Component, OnInit } from '@angular/core'; import { ActivatedRoute, Router } from '@angular/router'; import { MatTabChangeEvent } from '@angular/material/tabs'; import {Location} from '@angular/common'; +import { AuthService } from '@gsrs-core/auth/auth.service'; @Component({ selector: 'app-admin', @@ -12,38 +13,98 @@ export class AdminComponent implements OnInit { activeTab: number; current: string; lastTab: number; + canManageCVs: boolean = false; + canRunJobs: boolean = false; + canImportData: boolean =false; + canManageUsers: boolean = false; + canViewServerFiles:boolean = false; + constructor( private activatedRoute: ActivatedRoute, private router: Router, - private location: Location + private location: Location, + private authService: AuthService ) { } - ngOnInit() { + async ngOnInit() { console.log(`in admin.component ngOnInit`); + await this.checkPrivileges(); + this.activatedRoute.params.subscribe(routeParams => { this.current = routeParams.function; console.log(`routeParams.function: ${routeParams.function}`); switch (this.current) { case 'cache': this.activeTab = 0; break; - case 'info': this.activeTab = 1; break; - case 'user': this.activeTab = 2; break; - case 'import': this.activeTab = 3; break; - case 'cv': this.activeTab = 4; break; - case 'jobs': this.activeTab = 5; break; - case 'files': this.activeTab = 6; break; - case 'data': this.activeTab = 7; break; + case 'info': + this.activeTab = 1; break; + case 'user': + if(!this.canManageUsers){ + console.log("user does not have privs to manage users"); + this.activeTab=-1; + break; + } + this.activeTab = 2; break; + case 'import': + if( !this.canImportData ) { + console.log("user does not have privs to import data"); + this.activeTab=-1; + break; + } + this.activeTab = 3; + break; + + case 'cv': + if( !this.canManageCVs ) { + console.log("user does not have privs to manage CVs"); + this.activeTab=-1; + break; + } + this.activeTab = 4; + break; + + case 'jobs': + if(!this.canRunJobs){ + console.log("user does not have privs to run jobs"); + this.activeTab=-1; + break; + } + this.activeTab = 5; break; + case 'files': + if( !this.canViewServerFiles){ + console.log("user does not have privs to view server files"); + this.activeTab=-1; + break; + } this.activeTab = 6; break; + case 'data': + if( !this.canImportData){ + console.log("user does not have privs to import data"); + this.activeTab=-1; + break; + } + this.activeTab = 7; break; default: this.activeTab = 0; break; } + if( this.activeTab <= -1) { + this.router.navigate(['/home' ] ); + } }); - console.log(`this tab ${this.activeTab}`); const tab = this.activatedRoute.snapshot.queryParams['function'] || 'cache'; - + console.log('ngoninit complete at ' + (new Date())); } +async checkPrivileges() { + this.canManageCVs = await this.authService.hasSpecificPrivilege("Manage CVs"); + this.canRunJobs = await this.authService.hasSpecificPrivilege("Run Tasks"); + this.canImportData= await this.authService.hasSpecificPrivilege("Import Data"); + this.canManageUsers = await this.authService.hasSpecificPrivilege("Manage Users"); + this.canViewServerFiles = await this.authService.hasSpecificPrivilege("View Files"); + console.log('checkPrivileges complete'); +} onTabChanged(event: MatTabChangeEvent): void { + console.log(`starting onTabChanged at ` + (new Date())); let route = 'cache'; switch (event.index) { @@ -71,15 +132,18 @@ export class AdminComponent implements OnInit { route = 'data'; break; } - if (this.current !== 'jobs') { - this.current = route; - this.router.navigate(['/admin/' + route] ); + if( this.current !== route){ + if (this.current !== 'jobs') { + this.current = route; + this.router.navigate(['/admin/' + route] ); + } else { + this.current = route; + this.activeTab = 0; + this.router.navigate(['/admin/' + route] ); + } } else { - this.current = route; - this.activeTab = 0; - this.router.navigate(['/admin/' + route] ); + console.log('already on the desired tab!'); } - } } diff --git a/src/app/core/admin/cv-management/cv-term-dialog/cv-term-dialog.component.ts b/src/app/core/admin/cv-management/cv-term-dialog/cv-term-dialog.component.ts index 172b08b07..4b9ed6952 100644 --- a/src/app/core/admin/cv-management/cv-term-dialog/cv-term-dialog.component.ts +++ b/src/app/core/admin/cv-management/cv-term-dialog/cv-term-dialog.component.ts @@ -126,7 +126,8 @@ export class CvTermDialogComponent implements OnInit, AfterViewInit{ }, 200); } },error => { - let str = 'Invalid Vocabulary'; + let str = 'Error Updating Vocabulary'; + console.log(`error.error: ${error.error}; error.message: ${error.message}`); if (error.error && error.error.message) { str += '\n\n' + error.error.message; @@ -158,19 +159,6 @@ export class CvTermDialogComponent implements OnInit, AfterViewInit{ this.loading = false; }); - /* this.cvService.addVocabTerm( this.vocabulary).subscribe (response => { - this.loading = false; - if (response.terms && response.terms.length === this.vocabulary.terms.length) { - alert('vocabulary updated'); - setTimeout(() => { - this.dialogRef.close(response); - }, 200); - } else { - alert('invalid vocabulary'); - } - }, error => { - alert('invalid vocabulary'); - });*/ this.loading = false; } From bc7f1ff35d3c42529809ab6e6ce72e67483b3ae8 Mon Sep 17 00:00:00 2001 From: Mitch Miller Date: Mon, 27 Oct 2025 15:16:42 -0400 Subject: [PATCH 135/408] set up admin tabs so that those to which a user has no access (based on privs) are invisible --- src/app/core/admin/admin.component.html | 34 ++++----- src/app/core/admin/admin.component.ts | 92 ++++++++++++++++--------- 2 files changed, 75 insertions(+), 51 deletions(-) diff --git a/src/app/core/admin/admin.component.html b/src/app/core/admin/admin.component.html index 90349af0e..46cfb0311 100644 --- a/src/app/core/admin/admin.component.html +++ b/src/app/core/admin/admin.component.html @@ -1,22 +1,22 @@
    - + Server Status -

    Server Status

    +

    Server Status

    - - + + Service Information -

    Service Information

    +

    Service Information

    @@ -24,64 +24,64 @@

    Service Information

    - + User Management -

    User Management

    +

    User Management

    - + Data Import -

    Staged Data Import

    +

    Staged Data Import

    - + CV Management -

    Controlled Vocabulary Edit

    +

    Controlled Vocabulary Edit

    - + Scheduled Jobs -

    Scheduled Jobs

    +

    Scheduled Jobs

    - + All Files -

    All Files

    +

    All Files

    - + Data Management (Legacy) -

    Bulk Data Import

    +

    Bulk Data Import

    diff --git a/src/app/core/admin/admin.component.ts b/src/app/core/admin/admin.component.ts index ea71e39aa..e43f6c587 100644 --- a/src/app/core/admin/admin.component.ts +++ b/src/app/core/admin/admin.component.ts @@ -18,6 +18,7 @@ export class AdminComponent implements OnInit { canImportData: boolean =false; canManageUsers: boolean = false; canViewServerFiles:boolean = false; + canViewServiceInfo:boolean = false; constructor( private activatedRoute: ActivatedRoute, @@ -32,25 +33,27 @@ export class AdminComponent implements OnInit { this.activatedRoute.params.subscribe(routeParams => { this.current = routeParams.function; - console.log(`routeParams.function: ${routeParams.function}`); + console.log(`routeParams.function: ${routeParams.function} current: ${this.current} will call getActualTab`); + let actualTab = this.getActualTab(this.current); switch (this.current) { case 'cache': this.activeTab = 0; break; case 'info': - this.activeTab = 1; break; + this.activeTab = actualTab; + break; case 'user': if(!this.canManageUsers){ console.log("user does not have privs to manage users"); this.activeTab=-1; break; } - this.activeTab = 2; break; + this.activeTab = actualTab; break; case 'import': if( !this.canImportData ) { console.log("user does not have privs to import data"); this.activeTab=-1; break; } - this.activeTab = 3; + this.activeTab = actualTab; break; case 'cv': @@ -59,7 +62,7 @@ export class AdminComponent implements OnInit { this.activeTab=-1; break; } - this.activeTab = 4; + this.activeTab = actualTab; break; case 'jobs': @@ -68,20 +71,24 @@ export class AdminComponent implements OnInit { this.activeTab=-1; break; } - this.activeTab = 5; break; + this.activeTab = actualTab; + break; case 'files': if( !this.canViewServerFiles){ console.log("user does not have privs to view server files"); this.activeTab=-1; break; - } this.activeTab = 6; break; + } + this.activeTab = actualTab; + break; case 'data': if( !this.canImportData){ console.log("user does not have privs to import data"); this.activeTab=-1; break; } - this.activeTab = 7; break; + this.activeTab = actualTab; + break; default: this.activeTab = 0; break; } @@ -96,41 +103,22 @@ export class AdminComponent implements OnInit { async checkPrivileges() { this.canManageCVs = await this.authService.hasSpecificPrivilege("Manage CVs"); this.canRunJobs = await this.authService.hasSpecificPrivilege("Run Tasks"); - this.canImportData= await this.authService.hasSpecificPrivilege("Import Data"); + this.canImportData = await this.authService.hasSpecificPrivilege("Import Data"); this.canManageUsers = await this.authService.hasSpecificPrivilege("Manage Users"); this.canViewServerFiles = await this.authService.hasSpecificPrivilege("View Files"); + this.canViewServiceInfo = await this.authService.hasSpecificPrivilege("View Service Info"); + console.log(`canManageCVs: ${this.canManageCVs}; canRunJobs: ${this.canRunJobs}; canImportData: ${this.canImportData}; canManageUsers: ${this.canManageUsers}; canViewServerFiles: ${this.canViewServerFiles}`); console.log('checkPrivileges complete'); } onTabChanged(event: MatTabChangeEvent): void { - console.log(`starting onTabChanged at ` + (new Date())); + console.log(`starting onTabChanged event.index: ${event.index} at ` + (new Date())); let route = 'cache'; - switch (event.index) { - case 0: - break; - case 1: - route = 'info'; - break; - case 2: - route = 'user'; - break; - case 3: - route = 'import'; - break; - case 4: - route = 'cv'; - break; - case 5: - route = 'jobs'; - break; - case 6: - route = 'files'; - break; - case 7: - route = 'data'; - break; + let newRoute = this.getActualTabName(event.index); + if( newRoute.length > 0) { + route = newRoute; } if( this.current !== route){ if (this.current !== 'jobs') { @@ -146,4 +134,40 @@ async checkPrivileges() { } } + + getFilteredTabs() { + let allTabs = [ + {name: 'cache', available: this.canViewServiceInfo}, + {name: 'info', available: this.canViewServiceInfo}, + {name: 'user', available: this.canManageUsers}, + {name: 'import', available: this.canImportData}, + {name: 'cv', available: this.canManageCVs}, + {name: 'jobs', available: this.canRunJobs}, + {name: 'files', available: this.canViewServerFiles}, + {name: 'data', available: this.canImportData} + ] + + return allTabs.filter(t=>t.available); + } + + getActualTab(desiredFunctionality:string): number { + console.log(`getActualTab looking for ${desiredFunctionality}`); + + let filteredTabs = this.getFilteredTabs(); + for(var t=0; t< filteredTabs.length; t++) { + console.log(`filteredTabs[t].name: ${filteredTabs[t].name}; desiredFunctionality: ${desiredFunctionality}`); + if(filteredTabs[t].name == desiredFunctionality){ + console.log(`getActualTab about to return ${t} for input ${desiredFunctionality}`) + return t; + } + } + console.log(`getActualTab did not locate desire tab`); + return -1; + } + + getActualTabName(tabNumber: number) { + let filteredTabs = this.getFilteredTabs(); + if( tabNumber <0 || tabNumber >= filteredTabs.length) return ''; + return filteredTabs[tabNumber].name; + } } From 7ceeb7e27b905dd1f79be7f8d20705a770acc418 Mon Sep 17 00:00:00 2001 From: Mitch Miller Date: Tue, 28 Oct 2025 16:44:03 -0400 Subject: [PATCH 136/408] made the various 'can activate's make valid priv checks --- src/app/core/admin/can-activate-admin-page.ts | 43 ++++++++---------- src/app/core/admin/can-activate-admin.ts | 44 +++++++++--------- src/app/core/admin/can-import-data.ts | 43 ++++++++---------- src/app/core/app-routing.module.ts | 6 +-- src/app/core/auth/auth.service.ts | 1 - .../can-activate-substance-form.ts | 41 ++++++++--------- .../can-register-substance-form.ts | 39 ++++++++-------- ...ate-register-application-form.component.ts | 35 +++++++-------- ...ivate-update-application-form.component.ts | 45 +++++++++---------- .../application-loaded.component.ts | 2 - ...vate-register-impurities-form.component.ts | 31 ++++++------- ...tivate-update-impurities-form.component.ts | 39 +++++++--------- ...ter-invitro-pharmacology-form.component.ts | 39 +++++++--------- ...ate-invitro-pharmacology-form.component.ts | 39 +++++++--------- ...ctivate-register-product-form.component.ts | 39 +++++++--------- ...-activate-update-product-form.component.ts | 39 +++++++--------- 16 files changed, 233 insertions(+), 292 deletions(-) diff --git a/src/app/core/admin/can-activate-admin-page.ts b/src/app/core/admin/can-activate-admin-page.ts index 343466a9c..d8d06f784 100644 --- a/src/app/core/admin/can-activate-admin-page.ts +++ b/src/app/core/admin/can-activate-admin-page.ts @@ -12,32 +12,27 @@ export class CanActivateAdminPage implements CanActivate { private authService: AuthService ) {} - canActivate( + async canActivate( route: ActivatedRouteSnapshot, state: RouterStateSnapshot - ): Observable | Promise | (boolean | UrlTree) { - return new Observable(observer => { - this.authService.getAuth().subscribe(auth => { - if (auth) { - console.log(`going to check whether user has one of the required privs`); - if( this.authService.hasAnyPrivilege("Configure System", "Import Data", "Manage Users", "Manage CVs", "Run Tasks")) { - console.log('user CAN'); - observer.next(true); - observer.complete(); - } else { - observer.next(this.router.parseUrl('/admin')); - observer.complete(); - } - } else { - const navigationExtras: NavigationExtras = { - queryParams: { - path: state.url - } - }; - observer.next(this.router.createUrlTree(['/login'], navigationExtras)); - observer.complete(); + ): Promise { + const auth = this.authService.getAuth(); + if (auth) { + console.log(`going to check whether user has one of the required privs`); + const canDoSomethingAdmin= await this.authService.hasAnyPrivilege("Configure System", "Import Data", "Manage Users", "Manage CVs", "Run Tasks"); + if( canDoSomethingAdmin) { + console.log('user CAN'); + return true; + } else { + this.router.parseUrl('/home'); + } + } else { + const navigationExtras: NavigationExtras = { + queryParams: { + path: state.url } - }); - }); + }; + return this.router.createUrlTree(['/login'], navigationExtras); + } } } diff --git a/src/app/core/admin/can-activate-admin.ts b/src/app/core/admin/can-activate-admin.ts index 80b033afa..bb33e3c38 100644 --- a/src/app/core/admin/can-activate-admin.ts +++ b/src/app/core/admin/can-activate-admin.ts @@ -11,30 +11,28 @@ export class CanActivateAdmin implements CanActivate { private authService: AuthService ) { } - canActivate( + async canActivate( route: ActivatedRouteSnapshot, state: RouterStateSnapshot - ): Observable | Promise | (boolean | UrlTree) { - return new Observable(observer => { - this.authService.getAuth().subscribe(auth => { - if (auth) { - if(this.authService.hasSpecificPrivilege("Configure System")) { - observer.next(true); - observer.complete(); - } else { - observer.next(this.router.parseUrl('/home')); - observer.complete(); - } - } else { - const navigationExtras: NavigationExtras = { - queryParams: { - path: state.url - } - }; - observer.next(this.router.createUrlTree(['/login'], navigationExtras)); - observer.complete(); - } - }); - }); + ): Promise { + const auth = await this.authService.getAuth(); + if (auth) { + let canRunSomethingAdmin = await this.authService.hasAnyPrivilege('Configure System', 'Import Data', + 'Manage Users', 'Manage CVs', 'Run Tasks') + if( canRunSomethingAdmin) { + console.log(' has priv to configure system'); + return true; + } else { + this.router.parseUrl('/home'); + } + } + else { + const navigationExtras: NavigationExtras = { + queryParams: { + path: state.url + } + }; + return this.router.createUrlTree(['/login'], navigationExtras); + } } } diff --git a/src/app/core/admin/can-import-data.ts b/src/app/core/admin/can-import-data.ts index 5bb4c57cf..96d3318fc 100644 --- a/src/app/core/admin/can-import-data.ts +++ b/src/app/core/admin/can-import-data.ts @@ -10,33 +10,28 @@ export class CanImportData implements CanActivate { private authService: AuthService ) { } - canActivate( + async canActivate( route: ActivatedRouteSnapshot, state: RouterStateSnapshot - ): Observable | Promise | (boolean | UrlTree) { + ): Promise { console.log(`in CanImportData.canActivate`); - return new Observable(observer => { - this.authService.getAuth().subscribe(auth => { - if (auth) { - console.log(' got auth'); - if(this.authService.hasSpecificPrivilege("Import Data")) { - console.log(' has priv Import Data'); - observer.next(true); - observer.complete(); - } else { - observer.next(this.router.parseUrl('/home')); - observer.complete(); - } - } else { - const navigationExtras: NavigationExtras = { - queryParams: { - path: state.url - } - }; - observer.next(this.router.createUrlTree(['/login'], navigationExtras)); - observer.complete(); + const auth = this.authService.getAuth(); + if (auth) { + console.log(` got auth `); + const canImporNow = await this.authService.hasSpecificPrivilege("Import Data"); + if( canImporNow) { + console.log(' has priv \'Import Data\''); + return true; + } else { + this.router.parseUrl('/home'); + } + } else { + const navigationExtras: NavigationExtras = { + queryParams: { + path: state.url } - }); - }); + }; + return this.router.createUrlTree(['/login'], navigationExtras); + } } } \ No newline at end of file diff --git a/src/app/core/app-routing.module.ts b/src/app/core/app-routing.module.ts index 88201f9b1..dc4b8161a 100644 --- a/src/app/core/app-routing.module.ts +++ b/src/app/core/app-routing.module.ts @@ -107,13 +107,13 @@ const childRoutes: Routes = [ path: 'admin', component: AdminComponent, canActivate: [CanActivateAdmin], - children :[ - { + children :[ + { path: 'import', component: ImportManagementComponent, canActivate: [CanImportData] } - ] + ] }, { path: 'monitor/:id', diff --git a/src/app/core/auth/auth.service.ts b/src/app/core/auth/auth.service.ts index 4e0e704ab..c953173db 100644 --- a/src/app/core/auth/auth.service.ts +++ b/src/app/core/auth/auth.service.ts @@ -255,7 +255,6 @@ get auth(): Auth { console.log(`in canEditData, receives privs: ${JSON.stringify(privs)}`); return privs.some(p => p.privilege === 'Edit'); } - console.log(`starting canEditData. size of privs ${this._privileges.length}`); return this._privileges != null && this._privileges.some(p=>p.privilege=="Edit"); } diff --git a/src/app/core/substance-form/can-activate-substance-form.ts b/src/app/core/substance-form/can-activate-substance-form.ts index 292a8286a..eafd1a2b9 100644 --- a/src/app/core/substance-form/can-activate-substance-form.ts +++ b/src/app/core/substance-form/can-activate-substance-form.ts @@ -11,31 +11,26 @@ export class CanActivateSubstanceForm implements CanActivate { private authService: AuthService ) { } - canActivate( + async canActivate( route: ActivatedRouteSnapshot, state: RouterStateSnapshot - ): Observable | Promise | (boolean | UrlTree) { - return new Observable(observer => { - this.authService.getAuth().subscribe(auth => { - if (auth) { - console.log('in canActivate, going to check for Edit priv'); - if(this.authService.hasSpecificPrivilege('Edit')){ - observer.next(true); - observer.complete(); - } else { - observer.next(this.router.parseUrl('/browse-substance')); - observer.complete(); - } - } else { - const navigationExtras: NavigationExtras = { - queryParams: { - path: state.url - } - }; - observer.next(this.router.createUrlTree(['/login'], navigationExtras)); - observer.complete(); + ): Promise { + const auth = this.authService.getAuth(); + if (auth) { + console.log('in canActivate, going to check for Edit priv'); + const canEdit =await this.authService.hasSpecificPrivilege('Edit'); + if(canEdit){ + return true; + }else { + this.router.parseUrl('/browse-substance'); + } + } else { + const navigationExtras: NavigationExtras = { + queryParams: { + path: state.url } - }); - }); + }; + this.router.createUrlTree(['/login'], navigationExtras); + } } } diff --git a/src/app/core/substance-form/can-register-substance-form.ts b/src/app/core/substance-form/can-register-substance-form.ts index 99db371f5..7a9aa1321 100644 --- a/src/app/core/substance-form/can-register-substance-form.ts +++ b/src/app/core/substance-form/can-register-substance-form.ts @@ -11,30 +11,27 @@ export class CanRegisterSubstanceForm implements CanActivate { private authService: AuthService ) {} - canActivate( + async canActivate( route: ActivatedRouteSnapshot, state: RouterStateSnapshot - ): Observable | Promise | (boolean | UrlTree) { - return new Observable(observer => { - this.authService.getAuth().subscribe(auth => { - if (auth) { - if( this.authService.hasSpecificPrivilege('Create')) { - observer.next(true); - observer.complete(); - } else { - observer.next(this.router.parseUrl('/browse-substance')); - observer.complete(); - } - } else { - const navigationExtras: NavigationExtras = { + ): Promise { + + const auth = this.authService.getAuth(); + if (auth) { + console.log('in canActivate, going to check for Create priv'); + const canCreate =await this.authService.hasSpecificPrivilege('Create'); + if(canCreate){ + return true; + }else { + this.router.parseUrl('/browse-substance'); + } + } else { + const navigationExtras: NavigationExtras = { queryParams: { - path: state.url + path: state.url } - }; - observer.next(this.router.createUrlTree(['/login'], navigationExtras)); - observer.complete(); - } - }); - }); + }; + this.router.createUrlTree(['/login'], navigationExtras); + } } } diff --git a/src/app/fda/application/application-form/can-activate-register-application-form.component.ts b/src/app/fda/application/application-form/can-activate-register-application-form.component.ts index 96e85d822..59475314a 100644 --- a/src/app/fda/application/application-form/can-activate-register-application-form.component.ts +++ b/src/app/fda/application/application-form/can-activate-register-application-form.component.ts @@ -12,28 +12,27 @@ export class CanActivateRegisterApplicationFormComponent implements CanActivate private authService: AuthService ) {} - canActivate( + async canActivate( route: ActivatedRouteSnapshot, state: RouterStateSnapshot - ): Observable | Promise | (boolean | UrlTree) { - return new Observable(observer => { - this.authService.getAuth().pipe(take(1)).subscribe(auth => { - if (auth) { - if(this.authService.hasSpecificPrivilege('Create')) { - observer.next(true); - observer.complete(); - } + ): Promise { + const auth = this.authService.getAuth(); + if (auth) { + const canCreate =await this.authService.hasSpecificPrivilege('Create'); + if(canCreate){ + return true; } else { - const navigationExtras: NavigationExtras = { + this.router.parseUrl('/browse-applications'); + } + } else { + { + const navigationExtras: NavigationExtras = { queryParams: { - path: state.url + path: state.url } - }; - console.log('no auth '); - observer.next(this.router.createUrlTree(['/login'], navigationExtras)); - observer.complete(); - } - }); - }); + }; + this.router.createUrlTree(['/login'], navigationExtras); + } + } } } diff --git a/src/app/fda/application/application-form/can-activate-update-application-form.component.ts b/src/app/fda/application/application-form/can-activate-update-application-form.component.ts index a477499ed..2058c1a2c 100644 --- a/src/app/fda/application/application-form/can-activate-update-application-form.component.ts +++ b/src/app/fda/application/application-form/can-activate-update-application-form.component.ts @@ -15,36 +15,31 @@ export class CanActivateUpdateApplicationFormComponent implements CanActivate { ) { } - canActivate( + async canActivate( route: ActivatedRouteSnapshot, state: RouterStateSnapshot - ): Observable | Promise | (boolean | UrlTree) { - return new Observable(observer => { - const loadedComponents = this.configService.configData.loadedComponents || null; - if (loadedComponents && loadedComponents.applications) { - this.authService.getAuth().pipe(take(1)).subscribe(auth => { - if (auth) { - if(this.authService.hasSpecificPrivilege('Edit')) { - observer.next(true); - observer.complete(); - } else { - observer.next(this.router.parseUrl('/browse-applications')); - observer.complete(); - } + ): Promise { + const loadedComponents = this.configService.configData.loadedComponents || null; + if (loadedComponents && loadedComponents.applications) { + + const auth = this.authService.getAuth(); + if (auth) { + const canEdit =await this.authService.hasSpecificPrivilege('Edit'); + if(canEdit){ + return true; } else { - const navigationExtras: NavigationExtras = { - queryParams: { - path: state.url - } - }; - observer.next(this.router.createUrlTree(['/login'], navigationExtras)); - observer.complete(); + this.router.parseUrl('/browse-applications'); } - }); + } else { + const navigationExtras: NavigationExtras = { + queryParams: { + path: state.url + } + }; + this.router.createUrlTree(['/login'], navigationExtras); + } } else { - observer.next(this.router.parseUrl('/home')); - observer.complete(); + this.router.parseUrl('/home'); } - }); } } diff --git a/src/app/fda/application/application-loaded.component.ts b/src/app/fda/application/application-loaded.component.ts index 20c2b7282..9c679e9c5 100644 --- a/src/app/fda/application/application-loaded.component.ts +++ b/src/app/fda/application/application-loaded.component.ts @@ -16,7 +16,6 @@ export class ApplicationLoadedComponent implements CanActivate { console.log(`starting canActivate with route: ${route}`); return new Observable(observer => { const loadedComponents = this.configService.configData.loadedComponents || null; - console.log(`loadedComponents: ${JSON.stringify(loadedComponents)}`); if ( loadedComponents && loadedComponents.applications) { console.log(` true!`) observer.next(true); @@ -26,6 +25,5 @@ export class ApplicationLoadedComponent implements CanActivate { observer.complete(); } }); - } } diff --git a/src/app/fda/impurities/impurities-form/can-activate-register-impurities-form.component.ts b/src/app/fda/impurities/impurities-form/can-activate-register-impurities-form.component.ts index 4b2854b1f..3cc4d7fa2 100644 --- a/src/app/fda/impurities/impurities-form/can-activate-register-impurities-form.component.ts +++ b/src/app/fda/impurities/impurities-form/can-activate-register-impurities-form.component.ts @@ -12,30 +12,25 @@ export class CanActivateRegisterImpuritiesFormComponent implements CanActivate { private authService: AuthService ) {} - canActivate( + async canActivate( route: ActivatedRouteSnapshot, state: RouterStateSnapshot - ): Observable | Promise | (boolean | UrlTree) { - return new Observable(observer => { - this.authService.getAuth().pipe(take(1)).subscribe(auth => { - if (auth) { - if(this.authService.hasSpecificPrivilege('Edit')){ - observer.next(true); - observer.complete(); - } else { - observer.next(this.router.parseUrl('/home')); - observer.complete(); - } - } else { + ): Promise { + const auth =this.authService.getAuth(); + if (auth) { + const canEdit = await this.authService.hasSpecificPrivilege('Edit'); + if(canEdit){ + return true; + } else { + this.router.parseUrl('/home'); + } + } else { const navigationExtras: NavigationExtras = { queryParams: { path: state.url } }; - observer.next(this.router.createUrlTree(['/login'], navigationExtras)); - observer.complete(); - } - }); - }); + this.router.createUrlTree(['/login'], navigationExtras); + } } } diff --git a/src/app/fda/impurities/impurities-form/can-activate-update-impurities-form.component.ts b/src/app/fda/impurities/impurities-form/can-activate-update-impurities-form.component.ts index e6a9e61df..e753ede58 100644 --- a/src/app/fda/impurities/impurities-form/can-activate-update-impurities-form.component.ts +++ b/src/app/fda/impurities/impurities-form/can-activate-update-impurities-form.component.ts @@ -12,30 +12,25 @@ export class CanActivateUpdateImpuritiesFormComponent implements CanActivate { private authService: AuthService ) { } - canActivate( + async canActivate( route: ActivatedRouteSnapshot, state: RouterStateSnapshot - ): Observable | Promise | (boolean | UrlTree) { - return new Observable(observer => { - this.authService.getAuth().pipe(take(1)).subscribe(auth => { - if (auth) { - if( this.authService.hasSpecificPrivilege('Edit')){ - observer.next(true); - observer.complete(); - } else { - observer.next(this.router.parseUrl('/home')); - observer.complete(); - } - } else { - const navigationExtras: NavigationExtras = { - queryParams: { - path: state.url - } - }; - observer.next(this.router.createUrlTree(['/login'], navigationExtras)); - observer.complete(); + ): Promise { + const auth =this.authService.getAuth(); + if (auth) { + const canEdit = await this.authService.hasSpecificPrivilege('Edit'); + if(canEdit){ + return true; + } else { + this.router.parseUrl('/home'); + } + } else { + const navigationExtras: NavigationExtras = { + queryParams: { + path: state.url } - }); - }); + }; + this.router.createUrlTree(['/login'], navigationExtras); + } } } diff --git a/src/app/fda/invitro-pharmacology/invitro-pharmacology-form/can-activate-register-invitro-pharmacology-form.component.ts b/src/app/fda/invitro-pharmacology/invitro-pharmacology-form/can-activate-register-invitro-pharmacology-form.component.ts index 55345efc9..faabb82a2 100644 --- a/src/app/fda/invitro-pharmacology/invitro-pharmacology-form/can-activate-register-invitro-pharmacology-form.component.ts +++ b/src/app/fda/invitro-pharmacology/invitro-pharmacology-form/can-activate-register-invitro-pharmacology-form.component.ts @@ -13,30 +13,25 @@ export class CanActivateRegisterInvitroPharmacologyFormComponent implements CanA private authService: AuthService ) {} - canActivate( + async canActivate( route: ActivatedRouteSnapshot, state: RouterStateSnapshot - ): Observable | Promise | (boolean | UrlTree) { - return new Observable(observer => { - this.authService.getAuth().pipe(take(1)).subscribe(auth => { - if (auth) { - if(this.authService.hasSpecificPrivilege('Edit')) { - observer.next(true); - observer.complete(); - } else { - observer.next(this.router.parseUrl('/browse-invitro-pharm')); - observer.complete(); - } - } else { - const navigationExtras: NavigationExtras = { - queryParams: { - path: state.url - } - }; - observer.next(this.router.createUrlTree(['/login'], navigationExtras)); - observer.complete(); + ): Promise { + const auth =this.authService.getAuth(); + if (auth) { + const canEdit = await this.authService.hasSpecificPrivilege('Edit') + if(canEdit) { + return true; + } else { + this.router.parseUrl('/browse-invitro-pharm'); + } + } else { + const navigationExtras: NavigationExtras = { + queryParams: { + path: state.url } - }); - }); + }; + this.router.createUrlTree(['/login'], navigationExtras); + } } } diff --git a/src/app/fda/invitro-pharmacology/invitro-pharmacology-form/can-activate-update-invitro-pharmacology-form.component.ts b/src/app/fda/invitro-pharmacology/invitro-pharmacology-form/can-activate-update-invitro-pharmacology-form.component.ts index 86de1f37a..a9cd5f464 100644 --- a/src/app/fda/invitro-pharmacology/invitro-pharmacology-form/can-activate-update-invitro-pharmacology-form.component.ts +++ b/src/app/fda/invitro-pharmacology/invitro-pharmacology-form/can-activate-update-invitro-pharmacology-form.component.ts @@ -12,30 +12,25 @@ export class CanActivateUpdateInvitroPharmacologyFormComponent implements CanAct private authService: AuthService ) { } - canActivate( + async canActivate( route: ActivatedRouteSnapshot, state: RouterStateSnapshot - ): Observable | Promise | (boolean | UrlTree) { - return new Observable(observer => { - this.authService.getAuth().pipe(take(1)).subscribe(auth => { - if (auth) { - if( this.authService.hasSpecificPrivilege('Edit')){ - observer.next(true); - observer.complete(); - } else { - observer.next(this.router.parseUrl('/browse-invitro-pharm')); - observer.complete(); - } - } else { - const navigationExtras: NavigationExtras = { - queryParams: { - path: state.url - } - }; - observer.next(this.router.createUrlTree(['/login'], navigationExtras)); - observer.complete(); + ): Promise { + const auth = this.authService.getAuth(); + if (auth) { + const canRegister = await this.authService.hasSpecificPrivilege('Create'); + if(canRegister ){ + return true; + } else { + this.router.parseUrl('/browse-invitro-pharm'); + } + } else { + const navigationExtras: NavigationExtras = { + queryParams: { + path: state.url } - }); - }); + }; + this.router.createUrlTree(['/login'], navigationExtras); + } } } diff --git a/src/app/fda/product/product-form/can-activate-register-product-form.component.ts b/src/app/fda/product/product-form/can-activate-register-product-form.component.ts index cbbdf6108..288263978 100644 --- a/src/app/fda/product/product-form/can-activate-register-product-form.component.ts +++ b/src/app/fda/product/product-form/can-activate-register-product-form.component.ts @@ -13,30 +13,25 @@ export class CanActivateRegisterProductFormComponent implements CanActivate { private authService: AuthService ) {} - canActivate( + async canActivate( route: ActivatedRouteSnapshot, state: RouterStateSnapshot - ): Observable | Promise | (boolean | UrlTree) { - return new Observable(observer => { - this.authService.getAuth().pipe(take(1)).subscribe(auth => { - if (auth) { - if( this.authService.hasSpecificPrivilege('Edit')){ - observer.next(true); - observer.complete(); - } else { - observer.next(this.router.parseUrl('/browse-products')); - observer.complete(); - } - } else { - const navigationExtras: NavigationExtras = { - queryParams: { - path: state.url - } - }; - observer.next(this.router.createUrlTree(['/login'], navigationExtras)); - observer.complete(); + ): Promise { + const auth = this.authService.getAuth(); + if (auth) { + const canRegister = await this.authService.hasSpecificPrivilege('Create'); + if(canRegister){ + return true; + } else { + this.router.parseUrl('/browse-products'); + } + } else { + const navigationExtras: NavigationExtras = { + queryParams: { + path: state.url } - }); - }); + }; + this.router.createUrlTree(['/login'], navigationExtras); + } } } diff --git a/src/app/fda/product/product-form/can-activate-update-product-form.component.ts b/src/app/fda/product/product-form/can-activate-update-product-form.component.ts index 196575645..4e5020b84 100644 --- a/src/app/fda/product/product-form/can-activate-update-product-form.component.ts +++ b/src/app/fda/product/product-form/can-activate-update-product-form.component.ts @@ -12,30 +12,25 @@ export class CanActivateUpdateProductFormComponent implements CanActivate { private authService: AuthService ) { } - canActivate( + async canActivate( route: ActivatedRouteSnapshot, state: RouterStateSnapshot - ): Observable | Promise | (boolean | UrlTree) { - return new Observable(observer => { - this.authService.getAuth().pipe(take(1)).subscribe(auth => { - if (auth) { - if( this.authService.hasSpecificPrivilege('Edit')){ - observer.next(true); - observer.complete(); - } else { - observer.next(this.router.parseUrl('/browse-products')); - observer.complete(); - } - } else { - const navigationExtras: NavigationExtras = { - queryParams: { - path: state.url - } - }; - observer.next(this.router.createUrlTree(['/login'], navigationExtras)); - observer.complete(); + ): Promise { + const auth = this.authService.getAuth(); + if (auth) { + const canEdit = await this.authService.hasSpecificPrivilege('Edit'); + if(canEdit){ + return true; + } else { + this.router.parseUrl('/browse-products'); + } + } else { + const navigationExtras: NavigationExtras = { + queryParams: { + path: state.url } - }); - }); + }; + this.router.createUrlTree(['/login'], navigationExtras); + } } } From b2d8e3a497f728b0308fcb4f80fcd1d8b7ddea6d Mon Sep 17 00:00:00 2001 From: Mitch Miller Date: Tue, 28 Oct 2025 23:06:42 -0400 Subject: [PATCH 137/408] clear out roles when user logs off --- src/app/core/auth/auth.service.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/app/core/auth/auth.service.ts b/src/app/core/auth/auth.service.ts index c953173db..0c0b16fba 100644 --- a/src/app/core/auth/auth.service.ts +++ b/src/app/core/auth/auth.service.ts @@ -154,6 +154,7 @@ get auth(): Auth { // const url = (this.configService.configData && this.configService.configData.apiBaseUrl || '/') + 'logout'; // this.http.get(url).pipe(take(1)).subscribe(response => {}, error => {}); // } + this._privileges = []; if (isPlatformBrowser(this.platformId)) { sessionStorage.removeItem('authToken'); const cookies = document.cookie.split(';'); From 1e0a357f97e8523f8c0092e63e24538a0c7db8fb Mon Sep 17 00:00:00 2001 From: Mitch Miller Date: Wed, 29 Oct 2025 11:40:52 -0400 Subject: [PATCH 138/408] save roles when first creating user --- .../user-edit-dialog/user-edit-dialog.component.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/app/core/admin/user-management/user-edit-dialog/user-edit-dialog.component.ts b/src/app/core/admin/user-management/user-edit-dialog/user-edit-dialog.component.ts index 215a2dec4..5b129d6a6 100644 --- a/src/app/core/admin/user-management/user-edit-dialog/user-edit-dialog.component.ts +++ b/src/app/core/admin/user-management/user-edit-dialog/user-edit-dialog.component.ts @@ -235,9 +235,11 @@ export class UserEditDialogComponent implements OnInit { this.isError = false; if (this.newPassword === this.newPasswordConfirm) { const rolesArr = []; - this.roles.forEach(role => { - if (role.hasRole) { - rolesArr.push(role.name); + this.assignableRoles.forEach(role => { + console.log(`addUser evaluating role ${role.roleName}`); + if (role.assigned) { + console.log(`user will have role ${role.roleName}`); + rolesArr.push(role.roleName); } }); const groups = []; From 437b4eb9f764363e43a5392d2844be9a4e7c11b3 Mon Sep 17 00:00:00 2001 From: Mitch Miller Date: Mon, 3 Nov 2025 21:14:15 -0500 Subject: [PATCH 139/408] minor tweaks --- src/app/core/admin/admin.component.ts | 3 -- .../user-edit-dialog.component.ts | 3 +- src/app/core/auth/auth.service.ts | 31 ------------------- src/app/core/base/base.component.ts | 4 --- .../controlled-vocabulary.service.ts | 18 +++++++---- src/app/fda/config/config.json | 14 ++++----- 6 files changed, 21 insertions(+), 52 deletions(-) diff --git a/src/app/core/admin/admin.component.ts b/src/app/core/admin/admin.component.ts index e43f6c587..783529f6e 100644 --- a/src/app/core/admin/admin.component.ts +++ b/src/app/core/admin/admin.component.ts @@ -33,7 +33,6 @@ export class AdminComponent implements OnInit { this.activatedRoute.params.subscribe(routeParams => { this.current = routeParams.function; - console.log(`routeParams.function: ${routeParams.function} current: ${this.current} will call getActualTab`); let actualTab = this.getActualTab(this.current); switch (this.current) { case 'cache': this.activeTab = 0; break; @@ -155,9 +154,7 @@ async checkPrivileges() { let filteredTabs = this.getFilteredTabs(); for(var t=0; t< filteredTabs.length; t++) { - console.log(`filteredTabs[t].name: ${filteredTabs[t].name}; desiredFunctionality: ${desiredFunctionality}`); if(filteredTabs[t].name == desiredFunctionality){ - console.log(`getActualTab about to return ${t} for input ${desiredFunctionality}`) return t; } } diff --git a/src/app/core/admin/user-management/user-edit-dialog/user-edit-dialog.component.ts b/src/app/core/admin/user-management/user-edit-dialog/user-edit-dialog.component.ts index 5b129d6a6..6338c4c2d 100644 --- a/src/app/core/admin/user-management/user-edit-dialog/user-edit-dialog.component.ts +++ b/src/app/core/admin/user-management/user-edit-dialog/user-edit-dialog.component.ts @@ -145,7 +145,8 @@ export class UserEditDialogComponent implements OnInit { checkIfUserHasAdminRole(roles): boolean { let toReturn = false; roles.forEach(role => { - if(role.role.toLowerCase() === 'admin') { + console.log(`checkIfUserHasAdminRole role ${JSON.stringify(role)}`); + if(role.role && role.role.toLowerCase() === 'admin') { toReturn = true; } }); diff --git a/src/app/core/auth/auth.service.ts b/src/app/core/auth/auth.service.ts index 0c0b16fba..e4c38d035 100644 --- a/src/app/core/auth/auth.service.ts +++ b/src/app/core/auth/auth.service.ts @@ -21,7 +21,6 @@ export class AuthService { private http: HttpClient, @Inject(PLATFORM_ID) private platformId: any ) { - console.log(`starting AuthService constructor`); this.isLoading = true; configService.afterLoad().then(cs => { this.fetchAuth().pipe(take(1)).subscribe(auth => { @@ -36,7 +35,6 @@ export class AuthService { this._authUpdate.next(null); this.isLoading = false; }); - console.log('going to call fetchPrivs'); }); this.fetchPrivs().subscribe({ next: privs => { @@ -46,7 +44,6 @@ export class AuthService { console.error('Error fetching privileges:', err); }, complete: () => { - console.log('Privilege fetch complete'); return this._privileges; } }); @@ -135,25 +132,10 @@ get auth(): Auth { console.log("Error calling observer, registered error, passed null"); } }); - /* - this._authUpdate.subscribe(auth => { - observer.next(auth); - }, error => { - observer.next(null); - }); - */ }); } logout(): void { - // if ( - // !this.configService.configData - // || !this.configService.configData.apiBaseUrl - // || this.configService.configData.apiBaseUrl.startsWith('/') - // ) { - // const url = (this.configService.configData && this.configService.configData.apiBaseUrl || '/') + 'logout'; - // this.http.get(url).pipe(take(1)).subscribe(response => {}, error => {}); - // } this._privileges = []; if (isPlatformBrowser(this.platformId)) { sessionStorage.removeItem('authToken'); @@ -251,9 +233,7 @@ get auth(): Auth { async canEditData( ):Promise { if( this._privileges == null || this._privileges.length === 0) { - console.log(`in canEditData, privilege array is empty/null`); const privs = await firstValueFrom(this.fetchPrivs()); - console.log(`in canEditData, receives privs: ${JSON.stringify(privs)}`); return privs.some(p => p.privilege === 'Edit'); } return this._privileges != null && this._privileges.some(p=>p.privilege=="Edit"); @@ -338,8 +318,6 @@ get auth(): Auth { this.http.get(`${url}whoami`) .subscribe( auth => { - // console.log("Authorized as"); - // console.log(auth); observer.next(auth); }, err => { @@ -355,12 +333,10 @@ get auth(): Auth { } private fetchPrivs(): Observable { - console.log('starting fetchPrivs'); return from(this.configService.afterLoad()).pipe( switchMap(() => { const baseUrl = this.configService.configData?.apiBaseUrl || '/'; const url = `${baseUrl}api/v1/allmyprivs`; - console.log(`in switchMap, got url: ${url}`); return this.http.get(url); }), map(response => { @@ -375,11 +351,4 @@ private fetchPrivs(): Observable { ); } - - /* - private fetchAuth(): Observable { - const url = `${(this.configService.configData && this.configService.configData.apiBaseUrl) || '/'}api/v1/`; - return this.http.get(`${url}whoami`); - } - */ } diff --git a/src/app/core/base/base.component.ts b/src/app/core/base/base.component.ts index 2d6b38c49..2708723f7 100644 --- a/src/app/core/base/base.component.ts +++ b/src/app/core/base/base.component.ts @@ -152,11 +152,8 @@ export class BaseComponent implements OnInit, OnDestroy { } } this.canConfigureSystem = await this.authService.hasSpecificPrivilege('Configure System'); - console.log(`canConfigureSystem ${this.canConfigureSystem}`); this.canUserImportData = await this.authService.hasSpecificPrivilege('Import Data'); - console.log(`this.canUserImportData: ${this.canUserImportData}`); this.canRegister=await this.authService.canEditData(); - console.log(`in BaseComponent, canRegister: ${this.canRegister}`); this.canManageCVs = await this.authService.hasSpecificPrivilege("Manage CVs"); //not sure if we need this. // TODO: remove it and test that the component works. @@ -509,7 +506,6 @@ export class BaseComponent implements OnInit, OnDestroy { if (response) { this.loadingService.setLoading(true); - // console.log(response.json); const read = response.substance; diff --git a/src/app/core/controlled-vocabulary/controlled-vocabulary.service.ts b/src/app/core/controlled-vocabulary/controlled-vocabulary.service.ts index a441c3145..d7c8368b1 100644 --- a/src/app/core/controlled-vocabulary/controlled-vocabulary.service.ts +++ b/src/app/core/controlled-vocabulary/controlled-vocabulary.service.ts @@ -111,12 +111,18 @@ export class ControlledVocabularyService extends BaseHttpService { return this.http.get(url); } getStructureUrl(structure: string) { - structure = structure.replace(/[;]/g, '%3B') - .replace(/[#]/g, '%23') - .replace(/[+]/g, '%2B') - .replace(/[|]/g, '%7C'); - const url = this.baseUrl + 'render?structure=' + structure + '&size=150&standardize=true'; - return url; + if(structure && structure !== null) { + structure = structure.replace(/[;]/g, '%3B') + .replace(/[#]/g, '%23') + .replace(/[+]/g, '%2B') + .replace(/[|]/g, '%7C'); + const url = this.baseUrl + 'render?structure=' + structure + '&size=150&standardize=true'; + return url; + } else { + console.log(`in getStructureUrl received empty structure`); + } + return ""; + } getStructureUrlFragment(structure: string) { diff --git a/src/app/fda/config/config.json b/src/app/fda/config/config.json index 0899d50c4..a16f483b3 100644 --- a/src/app/fda/config/config.json +++ b/src/app/fda/config/config.json @@ -91,16 +91,16 @@ "userRegistration": false }, "services": [ - { "name": "adverse-events", "active": false, "hasEntities": true }, - { "name": "applications", "active": false, "hasEntities": true }, - { "name": "clinical-trials", "active": false, "hasEntities": true }, + { "name": "adverse-events", "active": true, "hasEntities": true }, + { "name": "applications", "active": true, "hasEntities": true }, + { "name": "clinical-trials", "active": true, "hasEntities": true }, { "name": "discovery", "active": false, "hasEntities": false }, { "name": "frontend", "active": true, "hasEntities": false }, { "name": "gateway", "active": true, "hasEntities": false }, - { "name": "impurities", "active": false, "hasEntities": true }, - { "name": "invitro-pharmacology", "active": false, "hasEntities": true }, - { "name": "products", "active": false, "hasEntities": true }, - { "name": "ssg4m", "active": false, "hasEntities": true }, + { "name": "impurities", "active": true, "hasEntities": true }, + { "name": "invitro-pharmacology", "active":true, "hasEntities": true }, + { "name": "products", "active": true, "hasEntities": true }, + { "name": "ssg4m", "active": true, "hasEntities": true }, { "name": "substances", "active": true, "hasEntities": true } ], "usefulLinks": [ From 507c0fa63a2f939745f902c5f407f922989b97ee Mon Sep 17 00:00:00 2001 From: Newatia Date: Tue, 4 Nov 2025 12:00:01 -0500 Subject: [PATCH 140/408] fixed few items --- .../assets/data/application_dictionary.json | 2 +- .../core/assets/data/product_dictionary.json | 2 +- .../core/bulk-search/bulk-query.component.ts | 7 + .../core/bulk-search/bulk-search.component.ts | 2 +- .../substance-selector.component.ts | 30 +++- src/app/core/substance/substance.service.ts | 21 ++- .../substances-browse.component.html | 33 ++-- .../advanced-search.component.ts | 7 +- .../applications-browse.component.html | 39 ++++- .../service/application.service.ts | 15 +- src/app/fda/config/config.json | 24 ++- .../impurities-details.component.html | 2 +- .../impurities-details.component.ts | 1 + .../impurities-details-form.component.ts | 2 - .../impurities-form.component.html | 9 + .../impurities-form.component.ts | 157 ++++++++++++------ .../impurities-test-form.component.html | 25 +-- .../impurities-test-form.component.ts | 1 + src/app/fda/impurities/impurities.module.ts | 2 + .../fda/impurities/model/impurities.model.ts | 1 + ...harmacology-assay-data-import.component.ts | 40 ++++- .../invitro-pharmacology-form.component.ts | 45 ++++- .../products-browse.component.html | 31 ++-- .../fda/product/service/product.service.ts | 21 ++- 24 files changed, 383 insertions(+), 136 deletions(-) diff --git a/src/app/core/assets/data/application_dictionary.json b/src/app/core/assets/data/application_dictionary.json index 69056cb1a..c8351fc15 100644 --- a/src/app/core/assets/data/application_dictionary.json +++ b/src/app/core/assets/data/application_dictionary.json @@ -235,7 +235,7 @@ "lucenePath":"root_applicationProductList_routeAdmin", "description":"Route of Administration for Application", "type":"string", - "cvDomain":null, + "cvDomain":"PROD_ROUTE_OF_ADMIN", "priority":"x", "suggest":null }, diff --git a/src/app/core/assets/data/product_dictionary.json b/src/app/core/assets/data/product_dictionary.json index d176f6c24..a0cddb237 100644 --- a/src/app/core/assets/data/product_dictionary.json +++ b/src/app/core/assets/data/product_dictionary.json @@ -773,7 +773,7 @@ "type":"string", "cvDomain":null, "priority":"x", - "suggest":null + "suggest":"Product_Name" }, "Product Name Language":{ "lucenePath":"root_productProvenances_productNames_language", diff --git a/src/app/core/bulk-search/bulk-query.component.ts b/src/app/core/bulk-search/bulk-query.component.ts index 32ef2dc0a..fb4bc5d02 100644 --- a/src/app/core/bulk-search/bulk-query.component.ts +++ b/src/app/core/bulk-search/bulk-query.component.ts @@ -224,7 +224,14 @@ submitText() { searchEntity: this.searchEntity } }; + + if (this.searchEntity && this.searchEntity === 'substances') { this.router.navigate(['/browse-substance'], navigationExtras); + } else if (this.searchEntity && this.searchEntity === 'products') { + this.router.navigate(['/browse-products'], navigationExtras); + } else if (this.searchEntity && this.searchEntity === 'applications') { + this.router.navigate(['/browse-applications'], navigationExtras); + } }, error => { console.log('Error trying to post/put a bulk query.'); const notification: AppNotification = { diff --git a/src/app/core/bulk-search/bulk-search.component.ts b/src/app/core/bulk-search/bulk-search.component.ts index a50f58e83..c025770c9 100644 --- a/src/app/core/bulk-search/bulk-search.component.ts +++ b/src/app/core/bulk-search/bulk-search.component.ts @@ -61,7 +61,7 @@ import { BulkSearch } from './bulk-search.model'; } ngOnInit() { - + alert("GGGGGGGGGGGGGGGGG"); this.loadingService.setLoading(true); this.showSpinner = true; // Start progress spinner diff --git a/src/app/core/substance-selector/substance-selector.component.ts b/src/app/core/substance-selector/substance-selector.component.ts index 601023da7..504ff40f8 100644 --- a/src/app/core/substance-selector/substance-selector.component.ts +++ b/src/app/core/substance-selector/substance-selector.component.ts @@ -99,12 +99,34 @@ export class SubstanceSelectorComponent implements OnInit { processSubstanceSearch(searchValue: string = ''): void { const q = searchValue.replace('\"', ''); const searchStr = this.substanceSelectorProperties.map(property => `${property}:\"^${q}$\"`).join(' OR '); - this.substanceService.getQuickSubstancesSummaries(searchStr, true).subscribe(response => { + if (response.content && response.content.length) { - this.selectedSubstance = response.content[0]; - this.selectionUpdated.emit(this.selectedSubstance); - this.errorMessage = ''; + + let found = false; + // Loop through the search results and compare the search term with search result's _name field + for (let i = 0; i < response.content.length; i++) { + let substance = response.content[i]; + if (substance._name && substance._name === searchValue) { + + // set to true, since search value matches with search result's _name field + found = true; + + this.selectedSubstance = substance; + this.selectionUpdated.emit(this.selectedSubstance); + + // break out of the loop when name found + break; + } + } + + // If Ingredient Name not found into the database, display message + if (found == false) { + this.errorMessage = 'No substances found'; + } else { + this.errorMessage = ''; + } + } else { this.errorMessage = 'No substances found'; } diff --git a/src/app/core/substance/substance.service.ts b/src/app/core/substance/substance.service.ts index 745cb077c..7ac1ae194 100644 --- a/src/app/core/substance/substance.service.ts +++ b/src/app/core/substance/substance.service.ts @@ -577,7 +577,8 @@ export class SubstanceService extends BaseHttpService { skip, view, simpleSearchOnly, - viewfield + viewfield, + order ); } else { // consider making API backend provide statusKey in JSON @@ -607,7 +608,8 @@ export class SubstanceService extends BaseHttpService { skip?: number, view?: string, simpleSearchOnly?: boolean, - viewfield?: string + viewfield?: string, + order?: string ): void { this.tempObject = { querySearchTerm: querySearchTerm, @@ -621,9 +623,10 @@ export class SubstanceService extends BaseHttpService { skip: skip ? skip : 0, view: view ? view : null, simpleSearchOnly: simpleSearchOnly ? simpleSearchOnly : null, - viewfield: viewfield ? viewfield : null + viewfield: viewfield ? viewfield : null, + order: order ? order : null } - this.getAsyncSearchResults(querySearchTerm, searchKey, pageSize, facets, skip, view, simpleSearchOnly, viewfield) + this.getAsyncSearchResults(querySearchTerm, searchKey, pageSize, facets, skip, view, simpleSearchOnly, viewfield, order) .pipe( switchMap(response => { let temp: any = response; @@ -656,7 +659,8 @@ export class SubstanceService extends BaseHttpService { skip, view, simpleSearchOnly, - viewfield + viewfield, + order ); }); }, @@ -682,7 +686,8 @@ export class SubstanceService extends BaseHttpService { skip?: number, view?: string, simpleSearchOnly?: boolean, - viewfield?: string + viewfield?: string, + order?: string ): any { const url = `${this.apiBaseUrl}status(${structureSearchKey})/results`; let params = new FacetHttpParams({ encoder: new CustomEncoder() }); @@ -712,6 +717,10 @@ export class SubstanceService extends BaseHttpService { params = params.append('q', querySearchTerm); } + if (order != null && order !== '') { + params = params.append('order', order); + } + const options = { params: params }; diff --git a/src/app/core/substances-browse/substances-browse.component.html b/src/app/core/substances-browse/substances-browse.component.html index 4b3f106ae..960332fa6 100644 --- a/src/app/core/substances-browse/substances-browse.component.html +++ b/src/app/core/substances-browse/substances-browse.component.html @@ -430,23 +430,24 @@
    -
    - -
    - -
    + +
    + +
    + +
    - -
    - -
    -
    + +
    + +
    +
    diff --git a/src/app/fda/advanced-search/advanced-search.component.ts b/src/app/fda/advanced-search/advanced-search.component.ts index c9e14bbeb..f303cf455 100644 --- a/src/app/fda/advanced-search/advanced-search.component.ts +++ b/src/app/fda/advanced-search/advanced-search.component.ts @@ -734,8 +734,11 @@ export class AdvancedSearchComponent implements OnInit, OnDestroy { } processSearch(): void { - // this.storeCriteriaInLocalStorage(); - + + if (!this.query) { + alert("Please enter search value in the textbox"); + } + const queryStatementHashes = []; // Store in cookies, Category tab (Substance, Application, etc) diff --git a/src/app/fda/application/applications-browse/applications-browse.component.html b/src/app/fda/application/applications-browse/applications-browse.component.html index f743b9097..5c15863a2 100644 --- a/src/app/fda/application/applications-browse/applications-browse.component.html +++ b/src/app/fda/application/applications-browse/applications-browse.component.html @@ -247,7 +247,7 @@ eventCategory="applicationSearch"> -
    +
    -
    + *ngIf="(impuritiesTest.elutionType && (impuritiesTest.elutionType.toUpperCase() === ELUTION_TYPE_ISOCRATIC || impuritiesTest.elutionType.toUpperCase() === ELUTION_TYPE_GRADIENT)) && impuritiesTest.impuritiesSolutionList.length > 0">
    @@ -272,7 +273,7 @@ -
    @@ -355,7 +356,7 @@
    -
    +
    diff --git a/src/app/fda/impurities/impurities-form/impurities-test-form/impurities-test-form.component.ts b/src/app/fda/impurities/impurities-form/impurities-test-form/impurities-test-form.component.ts index 3895be7e4..d5c14fb0b 100644 --- a/src/app/fda/impurities/impurities-form/impurities-test-form/impurities-test-form.component.ts +++ b/src/app/fda/impurities/impurities-form/impurities-test-form/impurities-test-form.component.ts @@ -19,6 +19,7 @@ import { ConfirmDialogComponent } from '../../../confirm-dialog/confirm-dialog.c export class ImpuritiesTestFormComponent implements OnInit, OnDestroy { public ELUTION_TYPE_ISOCRATIC = 'ISOCRATIC'; + public ELUTION_TYPE_GRADIENT = 'GRADIENT'; @Input() impuritiesTest: ImpuritiesTesting; @Input() impuritiesTestIndex: number; diff --git a/src/app/fda/impurities/impurities.module.ts b/src/app/fda/impurities/impurities.module.ts index d7d0a9e7c..1483ce85d 100644 --- a/src/app/fda/impurities/impurities.module.ts +++ b/src/app/fda/impurities/impurities.module.ts @@ -12,6 +12,7 @@ import { MatBadgeModule } from '@angular/material/badge'; import { MatTooltipModule } from '@angular/material/tooltip'; import { MatExpansionModule } from '@angular/material/expansion'; import { MatTableModule } from '@angular/material/table'; +import { MatDatepickerModule } from '@angular/material/datepicker'; import { SubstanceFormModule } from '../../core/substance-form/substance-form.module'; import { SubstanceSearchSelectorModule } from '../substance-search-select/substance-search-selector.module'; import { SubstanceTextSearchModule } from '@gsrs-core/substance-text-search/substance-text-search.module'; @@ -82,6 +83,7 @@ const impurityRoutes: Routes = [ MatBadgeModule, MatExpansionModule, MatTableModule, + MatDatepickerModule, SubstanceFormModule, SubstanceTextSearchModule, SubstanceSearchSelectorModule, diff --git a/src/app/fda/impurities/model/impurities.model.ts b/src/app/fda/impurities/model/impurities.model.ts index e7ee01c73..fdc15fe63 100644 --- a/src/app/fda/impurities/model/impurities.model.ts +++ b/src/app/fda/impurities/model/impurities.model.ts @@ -17,6 +17,7 @@ export interface Impurities { internalVersion?: number; impuritiesSubstanceList?: Array; impuritiesTotal?: ImpuritiesTotal; + _dateTypeDate?: Date; } export interface ImpuritiesSubstance { diff --git a/src/app/fda/invitro-pharmacology/invitro-pharmacology-assay-data-import/invitro-pharmacology-assay-data-import.component.ts b/src/app/fda/invitro-pharmacology/invitro-pharmacology-assay-data-import/invitro-pharmacology-assay-data-import.component.ts index 69344dbee..64a12dafd 100644 --- a/src/app/fda/invitro-pharmacology/invitro-pharmacology-assay-data-import/invitro-pharmacology-assay-data-import.component.ts +++ b/src/app/fda/invitro-pharmacology/invitro-pharmacology-assay-data-import/invitro-pharmacology-assay-data-import.component.ts @@ -313,15 +313,19 @@ export class InvitroPharmacologyAssayDataImportComponent implements OnInit { // Loop through each Assay JSON Record, and save into the database this.importedAssayJson.forEach((element, index) => { + this.submitMessage = 'Validating Assay records in Excel file ' + (index + 1) + ' of ' + this.importedAssayJson.length + ', please wait .....'; + if (element) { let validationMessages: Array = []; + // Populate Target Name if Target Name Approval Id is available, and Target Name is empty + if (!element['targetName'] && element['targetNameApprovalId']) { + this.getSubstanceById(element, element['targetNameApprovalId'], this.TARGET_NAME, validationMessages, index); + } + const assay = JSON.parse(JSON.stringify(element)); this.invitroPharmacologyService.assay = assay; - this.submitMessage = 'Validating Assay records in Excel file ' + (index + 1) + ' of ' + this.importedAssayJson.length + ', please wait .....'; - - // Validate Assay const validateSubscription = this.invitroPharmacologyService.validateAssay().subscribe(response => { @@ -342,8 +346,17 @@ export class InvitroPharmacologyAssayDataImportComponent implements OnInit { if (validationMessagesResponse && validationMessagesResponse.length > 0) { validationMessagesResponse.forEach(validation => { + let isPush = true; if (validation) { - validationMessages.push(validation); + if (validation.message && validation.message === 'Target Name is required.') { + if (element["targetName"]) { + isPush = false; + } + } + + if (isPush == true) { + validationMessages.push(validation); + } } }); } @@ -597,6 +610,25 @@ export class InvitroPharmacologyAssayDataImportComponent implements OnInit { } } + getSubstanceById(element: any, approvalId: string, fieldName: string, validationMessages: Array, index: number) { + if (approvalId) { + this.generalService.getSubstanceByAnyId(approvalId).subscribe(substance => { + if (substance) { + if (substance._name) { + let substanceKey = this.generalService.getSubstanceKeyBySubstanceResolver(substance, this.substanceKeyTypeForInvitroPharmacologyConfig); + + if (fieldName == this.TARGET_NAME) { + element["targetName"] = substance._name; + element["targetNameSubstanceUuid"] = substance.uuid; + element["targetNameSubstanceKey"] = substanceKey; + element["targetNameSubstanceKeyType"] = this.substanceKeyTypeForInvitroPharmacologyConfig; + } + } + } + }); + } + } + showJSON(): void { const date = new Date(); let jsonFilename = 'invitro_pharm_bulk_assays_' + moment(date).format('MMM-DD-YYYY_H-mm-ss'); diff --git a/src/app/fda/invitro-pharmacology/invitro-pharmacology-form/invitro-pharmacology-form.component.ts b/src/app/fda/invitro-pharmacology/invitro-pharmacology-form/invitro-pharmacology-form.component.ts index 38eaeae9f..f97d5c8e5 100644 --- a/src/app/fda/invitro-pharmacology/invitro-pharmacology-form/invitro-pharmacology-form.component.ts +++ b/src/app/fda/invitro-pharmacology/invitro-pharmacology-form/invitro-pharmacology-form.component.ts @@ -486,7 +486,7 @@ export class InvitroPharmacologyFormComponent implements OnInit, OnDestroy { this.validateClient(); - // Set total assays to save + // Set total number of Assays to save this.totalAssayToSave = this.existingAssaysByAssaySetList.length; // If there is no error on client side, check validation on server side @@ -496,9 +496,11 @@ export class InvitroPharmacologyFormComponent implements OnInit, OnDestroy { // Validate Assay // this.invitroPharmacologyService.validateAssay().pipe(take(1)).subscribe(results => { this.submissionMessage = null; + // this.validationMessages = results.validationMessages.filter( // message => message.messageType.toUpperCase() === 'ERROR' || message.messageType.toUpperCase() === 'WARNING'); // this.validationResult = results.valid; + this.showSubmissionMessages = true; this.isLoading = false; this.loadingService.setLoading(this.isLoading); @@ -599,6 +601,34 @@ export class InvitroPharmacologyFormComponent implements OnInit, OnDestroy { this.setValidationMessage('Result is required'); } + // Copy the Assays/Screening to new variable + let copiedAssays = _.cloneDeep(this.existingAssaysByAssaySetList); + + copiedAssays.forEach((assay, indexAssay) => { + if (assay) { + assay.invitroAssayScreenings.forEach(screening => { + if (screening) { + if (screening.invitroAssayResult != null) { + // Test Agent Concentration must be a number + if (screening.invitroAssayResult.testAgentConcentration) { + if (this.isNumber(screening.invitroAssayResult.testAgentConcentration) === false) { + this.setValidationMessage('Test Agent Concentration must be a number in row ' + (indexAssay+1)); + } + } + + // Result Value must be a number + if (screening.invitroAssayResult.resultValue) { + if (this.isNumber(screening.invitroAssayResult.resultValue) === false) { + this.setValidationMessage('Result Value must be a number in row ' + (indexAssay+1)); + } + } + + } + } + }); + } + }); + if (this.validationMessages.length > 0) { this.showSubmissionMessages = true; this.loadingService.setLoading(false); @@ -1517,11 +1547,11 @@ export class InvitroPharmacologyFormComponent implements OnInit, OnDestroy { */ // Copy the assay to new variable - // let copyAssay = _.cloneDeep(this.existingAssaysByAssaySetList[indexCopyFromAssay]); + // let copyAssay = _.cloneDeep(this.existingAssaysByAssaySetList[indexCopyFromAssay]); copyAssay.invitroAssayScreenings.push(newScreening); - // this.existingAssaysByAssaySetList.splice(indexCopyFromAssay + 1, 0, copyAssay); + // this.existingAssaysByAssaySetList.splice(indexCopyFromAssay + 1, 0, copyAssay); } setPlasmaProteinCheckBox($event, screeningIndex: number): void { @@ -1613,6 +1643,15 @@ export class InvitroPharmacologyFormComponent implements OnInit, OnDestroy { } + isNumber(str: any): boolean { + if (str) { + const num = Number(str); + const nan = isNaN(num); + return !nan; + } + return false; + } + scrub(oldraw: any): any { const old = oldraw; const idHolders = jp.query(old, '$..[?(@.id)]'); diff --git a/src/app/fda/product/products-browse/products-browse.component.html b/src/app/fda/product/products-browse/products-browse.component.html index 450990afc..436b639f4 100644 --- a/src/app/fda/product/products-browse/products-browse.component.html +++ b/src/app/fda/product/products-browse/products-browse.component.html @@ -148,7 +148,8 @@
    - @@ -161,7 +162,8 @@
    - diff --git a/src/app/fda/product/service/product.service.ts b/src/app/fda/product/service/product.service.ts index d28f629e2..e6282ca27 100644 --- a/src/app/fda/product/service/product.service.ts +++ b/src/app/fda/product/service/product.service.ts @@ -95,7 +95,6 @@ export class ProductService extends BaseHttpService { return new Observable(observer => { if (bulkQID != null && bulkQID.toString() != '') { - // Perform bulk search this.productBulkSearch( searchTerm, @@ -218,7 +217,8 @@ export class ProductService extends BaseHttpService { options, pageSize, facets, - skip + skip, + order ); } else { observer.next(response); @@ -243,7 +243,8 @@ export class ProductService extends BaseHttpService { pageSize?: number, facets?: FacetParam, skip?: number, - view?: string + order?: string, + view?: string, ): void { // Get Buk Search Results this.getAsyncSearchResults( @@ -254,7 +255,8 @@ export class ProductService extends BaseHttpService { facets, skip, view, - bulkSearchResponse.results + bulkSearchResponse.results, + order ) .subscribe(bulkSearchStatusResponse => { // consider making API backend provide statusKey in JSON @@ -282,7 +284,8 @@ export class ProductService extends BaseHttpService { pageSize, facets, skip, - view + order, + view, ); }); }, error => { @@ -307,9 +310,11 @@ export class ProductService extends BaseHttpService { facets?: FacetParam, skip?: number, view?: string, - url?: string + url?: string, + order?: string ): any { + // Get Bulk Search Results url = this.getBulkSearchUrl(searchEntity, true); if (url) { @@ -334,6 +339,10 @@ export class ProductService extends BaseHttpService { params = params.append('q', querySearchTerm); } + if (order != null && order !== '') { + params = params.append('order', order); + } + const options = { params: params }; From be4629385e829609249c59a6102af8e09786d7b6 Mon Sep 17 00:00:00 2001 From: Newatia Date: Wed, 5 Nov 2025 14:20:15 -0500 Subject: [PATCH 141/408] updated Product and IVP --- ...harmacology-assay-data-import.component.ts | 20 ++++--- src/app/fda/product/model/product.model.ts | 1 + .../products-browse.component.html | 39 +++++++------ .../products-browse.component.ts | 56 ++++++++++++++++++- 4 files changed, 86 insertions(+), 30 deletions(-) diff --git a/src/app/fda/invitro-pharmacology/invitro-pharmacology-assay-data-import/invitro-pharmacology-assay-data-import.component.ts b/src/app/fda/invitro-pharmacology/invitro-pharmacology-assay-data-import/invitro-pharmacology-assay-data-import.component.ts index 64a12dafd..02bea473d 100644 --- a/src/app/fda/invitro-pharmacology/invitro-pharmacology-assay-data-import/invitro-pharmacology-assay-data-import.component.ts +++ b/src/app/fda/invitro-pharmacology/invitro-pharmacology-assay-data-import/invitro-pharmacology-assay-data-import.component.ts @@ -318,18 +318,18 @@ export class InvitroPharmacologyAssayDataImportComponent implements OnInit { if (element) { let validationMessages: Array = []; - // Populate Target Name if Target Name Approval Id is available, and Target Name is empty - if (!element['targetName'] && element['targetNameApprovalId']) { - this.getSubstanceById(element, element['targetNameApprovalId'], this.TARGET_NAME, validationMessages, index); - } - const assay = JSON.parse(JSON.stringify(element)); this.invitroPharmacologyService.assay = assay; // Validate Assay const validateSubscription = this.invitroPharmacologyService.validateAssay().subscribe(response => { - // Populated Substance Key and Substance Key Type for Target Name, Homolog, Substrate + // Populate 'Target Name' if 'Target Name Approval Id' is available, and 'Target Name' is empty + if (!element['targetName'] && element['targetNameApprovalId']) { + this.getTargetNameByApprovalId(element, element['targetNameApprovalId'], this.TARGET_NAME, validationMessages, index); + } + + // Populated 'Substance Key' and 'Substance Key Type' for Target Name, Homolog, Substrate if (element['targetName']) { this.getSubstanceNameDetails(element, element['targetName'], this.TARGET_NAME, validationMessages, index); } @@ -349,7 +349,7 @@ export class InvitroPharmacologyAssayDataImportComponent implements OnInit { let isPush = true; if (validation) { if (validation.message && validation.message === 'Target Name is required.') { - if (element["targetName"]) { + if (!element['targetName'] && element['targetNameApprovalId']) { isPush = false; } } @@ -610,7 +610,7 @@ export class InvitroPharmacologyAssayDataImportComponent implements OnInit { } } - getSubstanceById(element: any, approvalId: string, fieldName: string, validationMessages: Array, index: number) { + getTargetNameByApprovalId(element: any, approvalId: string, fieldName: string, validationMessages: Array, index: number) { if (approvalId) { this.generalService.getSubstanceByAnyId(approvalId).subscribe(substance => { if (substance) { @@ -625,7 +625,9 @@ export class InvitroPharmacologyAssayDataImportComponent implements OnInit { } } } - }); + }, error => { + this.setValidationMessage('Target Name is required.', validationMessages, index); + }); } } diff --git a/src/app/fda/product/model/product.model.ts b/src/app/fda/product/model/product.model.ts index 33aa5767b..5ce4d19a7 100644 --- a/src/app/fda/product/model/product.model.ts +++ b/src/app/fda/product/model/product.model.ts @@ -64,6 +64,7 @@ export interface ProductProvenance { productCompanies?: Array; productDocumentations?: Array; productIndications?: Array; + _applicationUrl?: string; } export interface ProductName { diff --git a/src/app/fda/product/products-browse/products-browse.component.html b/src/app/fda/product/products-browse/products-browse.component.html index 436b639f4..75df2a6be 100644 --- a/src/app/fda/product/products-browse/products-browse.component.html +++ b/src/app/fda/product/products-browse/products-browse.component.html @@ -378,18 +378,6 @@ -
    @@ -673,12 +661,27 @@ Application Type Number:
    diff --git a/src/app/fda/product/products-browse/products-browse.component.ts b/src/app/fda/product/products-browse/products-browse.component.ts index ef85c30bb..b41a29c25 100644 --- a/src/app/fda/product/products-browse/products-browse.component.ts +++ b/src/app/fda/product/products-browse/products-browse.component.ts @@ -319,6 +319,9 @@ export class ProductsBrowseComponent implements OnInit, AfterViewInit, OnDestroy // Get Daily Med Url if Product Code Type is NDC CODE this.getDailyMedUrlforProductCode(); + // Get Application Type and Application Number Url to go to Browse Application page. + this.getApplicationNumberTypeUrl(); + // Get list of Export extension options such as .xlsx, .txt this.productService.getExportOptions(this.etag).subscribe(response => { this.exportOptions = response; @@ -557,6 +560,46 @@ export class ProductsBrowseComponent implements OnInit, AfterViewInit, OnDestroy // this.substanceTextSearchService.setSearchValue('main-substance-search', this.privateSearchTerm); } + getApplicationNumberTypeUrl() { + this.products.forEach((product, indexProd) => { + product.productProvenances.forEach((prov, indexProv) => { + if ((prov.applicationType) && (prov.applicationNumber)) { + let truncateAppType = this.truncateBeforeNumber(prov.applicationNumber); + let truncateAppNum = this.truncateAfterAlpha(prov.applicationNumber); + if (prov.applicationType.toUpperCase() !== 'OTC MONOGRAPH FINAL' && prov.applicationType.toUpperCase() !== 'OTC MONOGRAPH NOT FINAL') { + prov._applicationUrl = 'root_appType:"^' + truncateAppType + '$" AND root_appNumber:"^' + truncateAppNum + '$"'; + } + } + }); + }); + } + + truncateBeforeNumber(str: string): string { + // Use search() with a regular expression to find the index of the first digit + const firstDigitIndex = str.search(/[0-9]/); + + // If a digit is found,slice the string up to that index + if (firstDigitIndex !== -1) { + return str.slice(0, firstDigitIndex); + } + + // If no digit is found, return the original string + return str; + } + + truncateAfterAlpha(str: string): string { + // Use search() with a regular expression to find the index of the first digit + const firstDigitIndex = str.search(/[0-9]/); + + // Get Number, If a digit is found, slice number + if (firstDigitIndex !== -1) { + return str.slice(firstDigitIndex, str.length); + } + + // If no digit is found, return the original string + return str; + } + getDailyMedUrlforProductCode(): void { this.products.forEach((product, indexProd) => { product.productProvenances.forEach((prov, indexProv) => { @@ -894,9 +937,17 @@ export class ProductsBrowseComponent implements OnInit, AfterViewInit, OnDestroy delete approvalIdHolders[i]._approvalId; } } - + + const applicationUrlHolders = jp.query(old, '$..[?(@._applicationUrl)]'); + for (let i = 0; i < applicationUrlHolders.length; i++) { + if (applicationUrlHolders[i]._applicationUrl) { + delete applicationUrlHolders[i]._applicationUrl; + } + } + delete old['_activeIngredients']; delete old['_otherIngredients']; + return old; } @@ -945,8 +996,7 @@ export class ProductsBrowseComponent implements OnInit, AfterViewInit, OnDestroy forwardToSubstance(bulkQID: number) { let currentUrl = this.location.path(); - alert('Current URL:' + currentUrl); - + // store values in array to retreive later from localStorage let item = { 'allSubFromProductUrl': currentUrl From 410816e41eeadf87be8fed39bde48aa6a4d2ed22 Mon Sep 17 00:00:00 2001 From: Newatia Date: Fri, 7 Nov 2025 11:25:05 -0500 Subject: [PATCH 142/408] updated advanced search, impurities, ivp --- .../advanced-search.component.html | 36 ++++++----------- .../advanced-search.component.scss | 26 ++++++++++++- .../advanced-search.component.ts | 32 ++++++++------- .../impurities-details.component.html | 39 +------------------ .../impurities-details.component.scss | 13 ++++++- .../impurities-form.component.html | 11 +----- .../impurities-form.component.ts | 2 +- ...harmacology-assay-data-import.component.ts | 18 +++++---- 8 files changed, 80 insertions(+), 97 deletions(-) diff --git a/src/app/fda/advanced-search/advanced-search.component.html b/src/app/fda/advanced-search/advanced-search.component.html index 3643c41b4..380d9827f 100644 --- a/src/app/fda/advanced-search/advanced-search.component.html +++ b/src/app/fda/advanced-search/advanced-search.component.html @@ -17,6 +17,11 @@

    + +
    + {{message}} +
    +
    @@ -116,11 +121,6 @@

    -
    @@ -148,11 +148,6 @@

    -
    @@ -172,7 +167,7 @@

    + All


    @@ -191,25 +186,23 @@

    - - -
    - + - AND Search Structure + AND Search Structure  (At least one search + value needs to be entered above, along with structure) +
    @@ -250,13 +243,6 @@

    -
    diff --git a/src/app/fda/advanced-search/advanced-search.component.scss b/src/app/fda/advanced-search/advanced-search.component.scss index 2d9bb2dd2..52c1a1e8f 100644 --- a/src/app/fda/advanced-search/advanced-search.component.scss +++ b/src/app/fda/advanced-search/advanced-search.component.scss @@ -128,10 +128,22 @@ padding: 10px 0px 5px 10px; } +.font11px { + font-size: 11px; +} + .font12px { font-size: 12px; } +.font14px { + font-size: 14px; +} + +.colorred { + color: var(--regular-red-color); +} + .colormaroon { color: rgb(151, 87, 87) } @@ -145,7 +157,11 @@ } .colorgray { - color: var(--maroon-color); + color: var(--regular-grey-color); +} + +.colorlightgray { + color: var(--regular-lightgray-color); } .colorwhite { @@ -184,6 +200,10 @@ margin-top: 70px; } +.marginleft10px { + margin-left: 10px; +} + .marginleft20px { margin-left: 20px; } @@ -216,6 +236,10 @@ margin-bottom: 5px; } +.marginbottom10px { + margin-bottom: 10px; +} + .padtop10px { padding-top: 50px; } diff --git a/src/app/fda/advanced-search/advanced-search.component.ts b/src/app/fda/advanced-search/advanced-search.component.ts index f303cf455..ff6ca7ac0 100644 --- a/src/app/fda/advanced-search/advanced-search.component.ts +++ b/src/app/fda/advanced-search/advanced-search.component.ts @@ -51,14 +51,6 @@ import { AdvancedSearchService } from './service/advanced-search.service'; styleUrls: ['./advanced-search.component.scss'] }) -/* -export interface FacetValueAdvanced { - label: string; - count: number; - url: string; -} -*/ - export class AdvancedSearchComponent implements OnInit, OnDestroy { loadedComponents: LoadedComponents; advancedSearchFacetDisplay = false; @@ -107,6 +99,8 @@ export class AdvancedSearchComponent implements OnInit, OnDestroy { dictionaryFileName: string; private subscriptions: Array = []; panelExpanded = false; + isStrcuturePanelOpen = false; + numFacetsLoaded = 0; // queryHash: number; queryStatementHashes: Array; @@ -134,6 +128,8 @@ export class AdvancedSearchComponent implements OnInit, OnDestroy { queryFacet = ''; queryDisplay = ''; facetNameText = ''; + message = ''; + facetDisplayType = 'all'; substanceFacetsDisplay = ['Record Status', 'Substance Class', 'Relationships', 'GInAS Tag']; applicationFacetsDisplay = ['Center', 'Application Type', 'Application Status', 'Provenance (GSRS)']; @@ -735,10 +731,7 @@ export class AdvancedSearchComponent implements OnInit, OnDestroy { processSearch(): void { - if (!this.query) { - alert("Please enter search value in the textbox"); - } - + this.message = ''; const queryStatementHashes = []; // Store in cookies, Category tab (Substance, Application, etc) @@ -770,7 +763,7 @@ export class AdvancedSearchComponent implements OnInit, OnDestroy { queryParams: {} }; - if ((this.query) || (Object.keys(this.privateFacetParams).length > 0)) { + if ((this.query) || ((this.privateFacetParams && Object.keys(this.privateFacetParams).length > 0))) { if (this.query) { if (this.category === 'Clinical Trial') { @@ -866,7 +859,10 @@ export class AdvancedSearchComponent implements OnInit, OnDestroy { this.router.navigate(['/browse-substance'], navigationExtras); } } else { - alert('Please select any criteria to search'); + if (!this.query) { + this.message = "Please enter search value in the textbox" + } + } } @@ -1000,5 +996,13 @@ export class AdvancedSearchComponent implements OnInit, OnDestroy { nameResolved(molfile: string): void { this.editor.setMolecule(molfile); } + + panelOpened() { + this.isStrcuturePanelOpen = true; + } + + panelClosed() { + this.isStrcuturePanelOpen = false; + } } diff --git a/src/app/fda/impurities/impurities-details/impurities-details.component.html b/src/app/fda/impurities/impurities-details/impurities-details.component.html index 08ce30238..ddef6992d 100644 --- a/src/app/fda/impurities/impurities-details/impurities-details.component.html +++ b/src/app/fda/impurities/impurities-details/impurities-details.component.html @@ -181,11 +181,7 @@
    - - -
    +
    Test {{(i + 1)}} of {{subs.impuritiesTestList.length}}  @@ -393,39 +389,6 @@
    - - - - -
    diff --git a/src/app/fda/impurities/impurities-details/impurities-details.component.scss b/src/app/fda/impurities/impurities-details/impurities-details.component.scss index b9bf7bd32..4d562b371 100644 --- a/src/app/fda/impurities/impurities-details/impurities-details.component.scss +++ b/src/app/fda/impurities/impurities-details/impurities-details.component.scss @@ -52,6 +52,7 @@ .row { display: flex; width: 100%; + max-width: 100%; border-bottom: solid 1px var(--box-shadow-color-3); /* &:not(:last-child) { @@ -83,6 +84,9 @@ max-width: 60%; padding: 7px; font-size: 12px; + word-wrap: break-word; + word-break: break-word; + overflow-wrap: break-word; } .row-property-2 { @@ -103,6 +107,9 @@ max-width: 80%; padding: 6px; font-size: 12px; + word-wrap: break-word; + word-break: break-word; + overflow-wrap: break-word; } .row-property-3 { @@ -125,6 +132,9 @@ font-size: 12px; word-wrap: break-word; text-align: left; + word-wrap: break-word; + word-break: break-word; + overflow-wrap: break-word; } .title { @@ -330,10 +340,11 @@ fieldset.border { border: solid 2px var(--fieldset-red-border-color)!important; padding: 0 10px 10px 10px; border-bottom: none; + max-width: 100%; } legend.border { - width: auto !important; + max-width: 100%; border: none; border-bottom: none; font-size: 14px; diff --git a/src/app/fda/impurities/impurities-form/impurities-form.component.html b/src/app/fda/impurities/impurities-form/impurities-form.component.html index 7eed08e91..aa84039be 100644 --- a/src/app/fda/impurities/impurities-form/impurities-form.component.html +++ b/src/app/fda/impurities/impurities-form/impurities-form.component.html @@ -183,16 +183,7 @@ (valueChange)="impurities.dateType = $event"> - - - + Date Type Date (mm/dd/yyyy) diff --git a/src/app/fda/impurities/impurities-form/impurities-form.component.ts b/src/app/fda/impurities/impurities-form/impurities-form.component.ts index 6547623fd..ebe78f0fa 100644 --- a/src/app/fda/impurities/impurities-form/impurities-form.component.ts +++ b/src/app/fda/impurities/impurities-form/impurities-form.component.ts @@ -342,7 +342,7 @@ export class ImpuritiesFormComponent implements OnInit, OnDestroy { // if the elutionType value is not 'Isocratic' or 'Gradient' and if there is data in Mobile Phase table, // empty the list if (elementTest.elutionType) { - if (elementTest.elutionType.toUpperCase() !== this.ELUTION_TYPE_ISOCRATIC || + if (elementTest.elutionType.toUpperCase() !== this.ELUTION_TYPE_ISOCRATIC && elementTest.elutionType.toUpperCase() !== this.ELUTION_TYPE_GRADIENT) { if (elementTest.impuritiesSolutionTableList) { if (elementTest.impuritiesSolutionTableList.length > 0) { diff --git a/src/app/fda/invitro-pharmacology/invitro-pharmacology-assay-data-import/invitro-pharmacology-assay-data-import.component.ts b/src/app/fda/invitro-pharmacology/invitro-pharmacology-assay-data-import/invitro-pharmacology-assay-data-import.component.ts index 02bea473d..b70915e95 100644 --- a/src/app/fda/invitro-pharmacology/invitro-pharmacology-assay-data-import/invitro-pharmacology-assay-data-import.component.ts +++ b/src/app/fda/invitro-pharmacology/invitro-pharmacology-assay-data-import/invitro-pharmacology-assay-data-import.component.ts @@ -324,11 +324,6 @@ export class InvitroPharmacologyAssayDataImportComponent implements OnInit { // Validate Assay const validateSubscription = this.invitroPharmacologyService.validateAssay().subscribe(response => { - // Populate 'Target Name' if 'Target Name Approval Id' is available, and 'Target Name' is empty - if (!element['targetName'] && element['targetNameApprovalId']) { - this.getTargetNameByApprovalId(element, element['targetNameApprovalId'], this.TARGET_NAME, validationMessages, index); - } - // Populated 'Substance Key' and 'Substance Key Type' for Target Name, Homolog, Substrate if (element['targetName']) { this.getSubstanceNameDetails(element, element['targetName'], this.TARGET_NAME, validationMessages, index); @@ -351,6 +346,7 @@ export class InvitroPharmacologyAssayDataImportComponent implements OnInit { if (validation.message && validation.message === 'Target Name is required.') { if (!element['targetName'] && element['targetNameApprovalId']) { isPush = false; + response.valid = true; } } @@ -361,6 +357,11 @@ export class InvitroPharmacologyAssayDataImportComponent implements OnInit { }); } + // Populate 'Target Name' if 'Target Name Approval Id' is available, and 'Target Name' is empty + if (!element['targetName'] && element['targetNameApprovalId']) { + this.getTargetNameByApprovalId(element, element['targetNameApprovalId'], this.TARGET_NAME, validationMessages, index); + } + const saved = { 'indexRecord': index, 'invitroAssaySets': assay.invitroAssaySets, 'externalAssaySource': assay.externalAssaySource, 'externalAssayId': assay.externalAssayId, 'targetName': assay.targetName, 'validationMessages': validationMessages, 'valid': response.valid } this.importValidateMessageArray.push(saved); @@ -622,12 +623,15 @@ export class InvitroPharmacologyAssayDataImportComponent implements OnInit { element["targetNameSubstanceUuid"] = substance.uuid; element["targetNameSubstanceKey"] = substanceKey; element["targetNameSubstanceKeyType"] = this.substanceKeyTypeForInvitroPharmacologyConfig; + + // Set the Target Name if found into the database by Target Name Approval ID + this.importValidateMessageArray[index].targetName = substance._name; } } } }, error => { - this.setValidationMessage('Target Name is required.', validationMessages, index); - }); + this.setValidationMessage('Target Name is required.', validationMessages, index); + }); } } From 2cce196b9536085c2c466df3597981dc5a6289ed Mon Sep 17 00:00:00 2001 From: Newatia Date: Fri, 7 Nov 2025 18:55:06 -0500 Subject: [PATCH 143/408] updated impurities --- .../impurities-details.component.html | 7 +++---- .../impurities-details.component.ts | 21 +++++++++++-------- ...harmacology-assay-data-import.component.ts | 2 +- src/app/fda/service/general.service.ts | 11 +++++++++- 4 files changed, 26 insertions(+), 15 deletions(-) diff --git a/src/app/fda/impurities/impurities-details/impurities-details.component.html b/src/app/fda/impurities/impurities-details/impurities-details.component.html index ddef6992d..2f0890db1 100644 --- a/src/app/fda/impurities/impurities-details/impurities-details.component.html +++ b/src/app/fda/impurities/impurities-details/impurities-details.component.html @@ -417,7 +417,7 @@ - + - - - + +
    {{columnName}} @@ -475,11 +475,10 @@

    diff --git a/src/app/fda/impurities/impurities-details/impurities-details.component.ts b/src/app/fda/impurities/impurities-details/impurities-details.component.ts index b8deb276c..083f5c311 100644 --- a/src/app/fda/impurities/impurities-details/impurities-details.component.ts +++ b/src/app/fda/impurities/impurities-details/impurities-details.component.ts @@ -35,12 +35,7 @@ export class ImpuritiesDetailsComponent implements OnInit, OnDestroy { message = ''; subRelationship: any; private subscriptions: Array = []; - - - displayedColumns = [ - 'Number', - 'Time (min)' - ] + displayedColumnsRow: string[][] = []; constructor( private activatedRoute: ActivatedRoute, @@ -103,7 +98,7 @@ export class ImpuritiesDetailsComponent implements OnInit, OnDestroy { // Get Substance Name for SubstanceUuid in ImpuritiesDetailsList this.impurities.impuritiesSubstanceList.forEach((elementRelSub) => { - elementRelSub.impuritiesTestList.forEach((elementRelTest) => { + elementRelSub.impuritiesTestList.forEach((elementRelTest, indexTest) => { elementRelTest.impuritiesDetailsList.forEach((elementRelImpuDet) => { if (elementRelImpuDet.relatedSubstanceUuid) { @@ -126,15 +121,23 @@ export class ImpuritiesDetailsComponent implements OnInit, OnDestroy { // add letter in the Mobile Phase column if (elementRelTest.impuritiesSolutionList) { if (elementRelTest.impuritiesSolutionList.length > 0) { + + let displayedColumns = [ + 'Number', + 'Time (min)' + ] + elementRelTest.impuritiesSolutionList.forEach(solution => { if (solution) { + if (solution.solutionLetter) { let columnName = 'Solution ' + solution.solutionLetter + ' (%)'; - this.displayedColumns.push(columnName); + displayedColumns.push(columnName); + + this.displayedColumnsRow[indexTest] = displayedColumns; } } }); - } // impuritiesSolutionTableList length > 0 } // impuritiesSolutionTableList exists }); // Loop: elementRelSub.impuritiesTestList diff --git a/src/app/fda/invitro-pharmacology/invitro-pharmacology-assay-data-import/invitro-pharmacology-assay-data-import.component.ts b/src/app/fda/invitro-pharmacology/invitro-pharmacology-assay-data-import/invitro-pharmacology-assay-data-import.component.ts index b70915e95..0f7d92683 100644 --- a/src/app/fda/invitro-pharmacology/invitro-pharmacology-assay-data-import/invitro-pharmacology-assay-data-import.component.ts +++ b/src/app/fda/invitro-pharmacology/invitro-pharmacology-assay-data-import/invitro-pharmacology-assay-data-import.component.ts @@ -613,7 +613,7 @@ export class InvitroPharmacologyAssayDataImportComponent implements OnInit { getTargetNameByApprovalId(element: any, approvalId: string, fieldName: string, validationMessages: Array, index: number) { if (approvalId) { - this.generalService.getSubstanceByAnyId(approvalId).subscribe(substance => { + this.generalService.getSubstanceByAnyIdFullView(approvalId).subscribe(substance => { if (substance) { if (substance._name) { let substanceKey = this.generalService.getSubstanceKeyBySubstanceResolver(substance, this.substanceKeyTypeForInvitroPharmacologyConfig); diff --git a/src/app/fda/service/general.service.ts b/src/app/fda/service/general.service.ts index 50e02e16e..84ddbf4e9 100644 --- a/src/app/fda/service/general.service.ts +++ b/src/app/fda/service/general.service.ts @@ -28,6 +28,15 @@ export class GeneralService extends BaseHttpService { super(configService); } + getSubstanceByAnyIdFullView(id: string): Observable { + const url = this.apiBaseUrl + 'substances(' + id + ')?view=full'; + return this.http.get(url).pipe( + map(results => { + return results; + }) + ); + } + getSubstanceByAnyId(id: string): Observable { const url = this.apiBaseUrl + 'substances(' + id + ')'; return this.http.get(url).pipe( @@ -54,7 +63,7 @@ export class GeneralService extends BaseHttpService { // If Substance Key Type is BDNUM in the frontend config, set value of Substance Key to Substance Bdnum/Code value // Get BDNUM from codes - if (substance.codes.length > 0) { + if (substance.codes && substance.codes.length > 0) { substance.codes.forEach((codeObj, index) => { if (codeObj) { if ((codeObj.codeSystem) && ((codeObj.codeSystem === 'BDNUM') && (codeObj.type === 'PRIMARY'))) { From c016ccb64f66e946b295dd9f68a9f5757ae46a15 Mon Sep 17 00:00:00 2001 From: Mitch Miller Date: Tue, 18 Nov 2025 21:38:02 -0500 Subject: [PATCH 144/408] check privileges for the advanced features on the substance edit page --- src/app/core/auth/auth.service.ts | 2 +- src/app/core/base/base.component.ts | 1 + .../substance-form.component.html | 27 ++++++++++--------- .../substance-form.component.ts | 5 +++- 4 files changed, 20 insertions(+), 15 deletions(-) diff --git a/src/app/core/auth/auth.service.ts b/src/app/core/auth/auth.service.ts index e4c38d035..c0b6c646c 100644 --- a/src/app/core/auth/auth.service.ts +++ b/src/app/core/auth/auth.service.ts @@ -241,7 +241,7 @@ get auth(): Auth { async hasAnyPrivilege(...privs) : Promise< boolean> { if( this._privileges == null || this._privileges.length === 0) { - const privs = await firstValueFrom(this.fetchPrivs()); + this._privileges = await firstValueFrom(this.fetchPrivs()); } return privs.some(p=>this._privileges.some(pp=>pp.privilege.toUpperCase()== p.toUpperCase())); } diff --git a/src/app/core/base/base.component.ts b/src/app/core/base/base.component.ts index 2708723f7..bc8d922e5 100644 --- a/src/app/core/base/base.component.ts +++ b/src/app/core/base/base.component.ts @@ -154,6 +154,7 @@ export class BaseComponent implements OnInit, OnDestroy { this.canConfigureSystem = await this.authService.hasSpecificPrivilege('Configure System'); this.canUserImportData = await this.authService.hasSpecificPrivilege('Import Data'); this.canRegister=await this.authService.canEditData(); + console.log(`canRegister: ${this.canRegister}; `); this.canManageCVs = await this.authService.hasSpecificPrivilege("Manage CVs"); //not sure if we need this. // TODO: remove it and test that the component works. diff --git a/src/app/core/substance-form/substance-form.component.html b/src/app/core/substance-form/substance-form.component.html index 5a9b0ea3c..f5b5bf229 100644 --- a/src/app/core/substance-form/substance-form.component.html +++ b/src/app/core/substance-form/substance-form.component.html @@ -27,35 +27,36 @@ - + Change Substance Class - + Change Status to approved - + Change Status to pending - + Set Definition to private - + Set Definition to public - + Un-approve record (Remove approval ID) + [disabled]="!id || status === 'pending' || (substanceClass === 'concept' && UNII === 'non-approved record')" + *ngIf="userCanApprove"> Change Approval ID - + Set concept status to non-approved - + Merge subconcept - + Switch primary and alt definitions @@ -66,14 +67,14 @@ Predict disulfide links by monoclonal antibody type - Register a Fragment - + Regenerate reference UUIDs - + Regenerate substance UUID diff --git a/src/app/core/substance-form/substance-form.component.ts b/src/app/core/substance-form/substance-form.component.ts index 641cb22dc..7d662f093 100644 --- a/src/app/core/substance-form/substance-form.component.ts +++ b/src/app/core/substance-form/substance-form.component.ts @@ -85,6 +85,7 @@ export class SubstanceFormComponent implements OnInit, AfterViewInit, OnDestroy serverError: boolean; canApprove: boolean; userCanApprove: boolean; + userCanMakePublic: boolean = false; approving: boolean; definition: SubstanceFormDefinition; user: string; @@ -311,9 +312,11 @@ export class SubstanceFormComponent implements OnInit, AfterViewInit, OnDestroy } if (this.configService.configData && this.configService.configData.useApprovalAPI) { this.useApprovalAPI = this.configService.configData.useApprovalAPI; - } this.canUpdate = await this.authService.hasSpecificPrivilege("Edit"); + } + this.canUpdate = await this.authService.hasSpecificPrivilege("Edit"); this.canMakeAdvancedEdits = await this.authService.hasSpecificPrivilege("Edit Public Data"); this.userCanApprove = await this.authService.hasSpecificPrivilege("Approve Records"); + this.userCanMakePublic = await this.authService.hasSpecificPrivilege('Make Records Public'); this.overlayContainer = this.overlayContainerService.getContainerElement(); this.imported = false; From 0219f5627094f1b4526cea4878bfe21f06e4b368 Mon Sep 17 00:00:00 2001 From: Jaroslav Iha Date: Thu, 20 Nov 2025 14:03:48 +0100 Subject: [PATCH 145/408] add: UUID generator PFDA-6362 --- src/app/core/substance-form/substance-form.component.html | 4 ++-- src/app/core/substance-form/substance-form.component.ts | 2 ++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/app/core/substance-form/substance-form.component.html b/src/app/core/substance-form/substance-form.component.html index aa021f55b..4c566991c 100644 --- a/src/app/core/substance-form/substance-form.component.html +++ b/src/app/core/substance-form/substance-form.component.html @@ -20,7 +20,7 @@ {{ showSubmissionMessages ? 'Hide' : 'Show' }} messages -
    +
    Advanced Features @@ -59,7 +59,7 @@ Switch primary and alt definitions - + Predict N-Glycosylation Sites diff --git a/src/app/core/substance-form/substance-form.component.ts b/src/app/core/substance-form/substance-form.component.ts index 6c1e357f7..9d1c47f9a 100644 --- a/src/app/core/substance-form/substance-form.component.ts +++ b/src/app/core/substance-form/substance-form.component.ts @@ -92,6 +92,7 @@ export class SubstanceFormComponent implements OnInit, AfterViewInit, OnDestroy isAdmin: boolean; isUpdater: boolean; messageField: string; + isPfdaVersion: boolean = false; uuid: string; substanceClass: string; drafts: Array; @@ -304,6 +305,7 @@ export class SubstanceFormComponent implements OnInit, AfterViewInit, OnDestroy } this.isAdmin = this.authService.hasRoles('admin'); this.isUpdater = this.authService.hasAnyRoles('Updater', 'SuperUpdater'); + this.isPfdaVersion = this.configService.configData.isPfdaVersion; this.overlayContainer = this.overlayContainerService.getContainerElement(); this.imported = false; if(this.location.path().includes('chemical-simplified')) { From 684cea52f6a38ac5d881bebd6b452713f1ed44bc Mon Sep 17 00:00:00 2001 From: Jaroslav Iha Date: Mon, 24 Nov 2025 13:15:35 +0100 Subject: [PATCH 146/408] test step view --- .../substance-ssg4m-form.component.ts | 23 ++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/src/app/core/substance-ssg4m/substance-ssg4m-form.component.ts b/src/app/core/substance-ssg4m/substance-ssg4m-form.component.ts index 4bf5023bf..23d05cbf7 100644 --- a/src/app/core/substance-ssg4m/substance-ssg4m-form.component.ts +++ b/src/app/core/substance-ssg4m/substance-ssg4m-form.component.ts @@ -1043,17 +1043,28 @@ export class SubstanceSsg4ManufactureFormComponent implements OnInit, AfterViewI async exportStepView(document: Document): Promise { const elementToConvert = document.querySelector('app-ssg4m-scheme-view') as HTMLElement; const clone = elementToConvert.cloneNode(true) as HTMLElement; + const initialWidth = elementToConvert.offsetWidth; + const initialHeight = elementToConvert.offsetHeight; const container = document.createElement('div'); container.style.position = 'absolute'; - container.style.left = '-9999px'; + container.style.left = '0px'; + container.style.padding = '0'; + container.style.margin = '0'; + container.style.display = 'inline-block'; const styles = this.getPageStyles(); container.innerHTML = styles; container.appendChild(clone); document.body.appendChild(container); + clone.style.overflow = 'visible'; + clone.style.maxWidth = 'none'; + clone.style.boxSizing = 'content-box'; await this.delay(2500) + const finalWidth = initialWidth; + const finalHeight = initialHeight; + function filter (node: HTMLElement) { if (!node.tagName) { return true; @@ -1063,8 +1074,8 @@ export class SubstanceSsg4ManufactureFormComponent implements OnInit, AfterViewI } const options = { filter: filter, - width: elementToConvert.offsetWidth, - height: elementToConvert.offsetHeight, + width: finalWidth, + height: finalHeight, fetchRequestInit: { headers: new Headers(), mode: 'cors' as RequestMode, @@ -1073,6 +1084,12 @@ export class SubstanceSsg4ManufactureFormComponent implements OnInit, AfterViewI }; const dataUrl = await toSvg(clone, options); + const downloadLink = document.createElement('a'); + downloadLink.href = dataUrl; + downloadLink.download = 'step_view.svg'; + document.body.appendChild(downloadLink); + downloadLink.click(); + document.body.removeChild(downloadLink); const commaIndex = dataUrl.indexOf(','); document.body.removeChild(container); return dataUrl.slice(commaIndex + 1); From e81529f08a76efd3368e30f509438163640c1389 Mon Sep 17 00:00:00 2001 From: Jaroslav Iha Date: Tue, 25 Nov 2025 14:36:27 +0100 Subject: [PATCH 147/408] fix step view generating --- .../substance-ssg4m-form.component.ts | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/app/core/substance-ssg4m/substance-ssg4m-form.component.ts b/src/app/core/substance-ssg4m/substance-ssg4m-form.component.ts index 23d05cbf7..78efd5963 100644 --- a/src/app/core/substance-ssg4m/substance-ssg4m-form.component.ts +++ b/src/app/core/substance-ssg4m/substance-ssg4m-form.component.ts @@ -1048,17 +1048,23 @@ export class SubstanceSsg4ManufactureFormComponent implements OnInit, AfterViewI const container = document.createElement('div'); container.style.position = 'absolute'; - container.style.left = '0px'; + container.style.left = '-9999px'; container.style.padding = '0'; container.style.margin = '0'; container.style.display = 'inline-block'; + container.style.width = `${initialWidth}px`; + container.style.height = `${initialHeight}px`; const styles = this.getPageStyles(); container.innerHTML = styles; container.appendChild(clone); document.body.appendChild(container); clone.style.overflow = 'visible'; clone.style.maxWidth = 'none'; - clone.style.boxSizing = 'content-box'; + clone.style.boxSizing = 'border-box'; + clone.style.fontFamily = 'Arial, sans-serif'; + clone.style.display = 'flex'; + clone.style.flexDirection = 'column'; + clone.style.alignItems = 'stretch'; await this.delay(2500) @@ -1086,7 +1092,7 @@ export class SubstanceSsg4ManufactureFormComponent implements OnInit, AfterViewI const dataUrl = await toSvg(clone, options); const downloadLink = document.createElement('a'); downloadLink.href = dataUrl; - downloadLink.download = 'step_view.svg'; + downloadLink.download = `ssg4m_step_view_${Date.now().toString(36) + Math.random().toString(36).substring(2, 9)}.svg`; document.body.appendChild(downloadLink); downloadLink.click(); document.body.removeChild(downloadLink); From e9483ab9a4723e7e279927ab1c46a105d0c5fcb8 Mon Sep 17 00:00:00 2001 From: Jaroslav Iha Date: Wed, 26 Nov 2025 14:11:37 +0100 Subject: [PATCH 148/408] disbale scheme view --- ...nce-form-ssg4m-process-card.component.html | 13 ------ ...tance-form-ssg4m-process-card.component.ts | 44 +++++++++---------- .../substance-ssg4m-form.component.ts | 30 +++++++++---- 3 files changed, 43 insertions(+), 44 deletions(-) diff --git a/src/app/core/substance-ssg4m/ssg4m-process/substance-form-ssg4m-process-card.component.html b/src/app/core/substance-ssg4m/ssg4m-process/substance-form-ssg4m-process-card.component.html index 22a5b4ef7..3c0afb53e 100644 --- a/src/app/core/substance-ssg4m/ssg4m-process/substance-form-ssg4m-process-card.component.html +++ b/src/app/core/substance-ssg4m/ssg4m-process/substance-form-ssg4m-process-card.component.html @@ -74,16 +74,3 @@ - - - -

    -     - -
    - - - -
    \ No newline at end of file diff --git a/src/app/core/substance-ssg4m/ssg4m-process/substance-form-ssg4m-process-card.component.ts b/src/app/core/substance-ssg4m/ssg4m-process/substance-form-ssg4m-process-card.component.ts index 465ffe461..5cc399657 100644 --- a/src/app/core/substance-ssg4m/ssg4m-process/substance-form-ssg4m-process-card.component.ts +++ b/src/app/core/substance-ssg4m/ssg4m-process/substance-form-ssg4m-process-card.component.ts @@ -207,28 +207,28 @@ export class SubstanceFormSsg4mProcessCardComponent extends SubstanceCardBaseFil onSelectedIndexChange(tabIndex: number) { this.tabSelectedIndex = tabIndex; - if (this.tabSelectedIndex === 2) { - document.querySelector("#scheme-viz-view").className = ""; - //This is a hacky placeholder way to force viz - //TODO finish this - const ssgjs = JSON.stringify(this.substanceFormService.cleanSubstance()); - - console.log("About to load the scheme view"); - if (window['schemeUtil']) { - if (window['schemeUtil'].debug) { - window['schemeUtil'].executeWhenLoaded = (() => { - console.log("About to render the scheme view"); - window['schemeUtil'].renderScheme(window['schemeUtil'].makeDisplayGraph(JSON.parse(ssgjs)), "#scheme-viz-view"); - window['schemeUtil'].executeWhenLoaded = null; - }); - } else { - console.log("About to render the scheme view"); - window['schemeUtil'].renderScheme(window['schemeUtil'].makeDisplayGraph(JSON.parse(ssgjs)), "#scheme-viz-view"); - } - } - } else { - document.querySelector("#scheme-viz-view").className = "hidden"; - } + // if (this.tabSelectedIndex === 2) { + // document.querySelector("#scheme-viz-view").className = ""; + // //This is a hacky placeholder way to force viz + // //TODO finish this + // const ssgjs = JSON.stringify(this.substanceFormService.cleanSubstance()); + + // console.log("About to load the scheme view"); + // if (window['schemeUtil']) { + // if (window['schemeUtil'].debug) { + // window['schemeUtil'].executeWhenLoaded = (() => { + // console.log("About to render the scheme view"); + // window['schemeUtil'].renderScheme(window['schemeUtil'].makeDisplayGraph(JSON.parse(ssgjs)), "#scheme-viz-view"); + // window['schemeUtil'].executeWhenLoaded = null; + // }); + // } else { + // console.log("About to render the scheme view"); + // window['schemeUtil'].renderScheme(window['schemeUtil'].makeDisplayGraph(JSON.parse(ssgjs)), "#scheme-viz-view"); + // } + // } + // } else { + // document.querySelector("#scheme-viz-view").className = "hidden"; + // } } tabSelectedIndexOutChange(tabIndex: number) { diff --git a/src/app/core/substance-ssg4m/substance-ssg4m-form.component.ts b/src/app/core/substance-ssg4m/substance-ssg4m-form.component.ts index 78efd5963..bcf30a31b 100644 --- a/src/app/core/substance-ssg4m/substance-ssg4m-form.component.ts +++ b/src/app/core/substance-ssg4m/substance-ssg4m-form.component.ts @@ -1005,6 +1005,17 @@ export class SubstanceSsg4ManufactureFormComponent implements OnInit, AfterViewI return new Promise(resolve => setTimeout(resolve, ms)); } + generateTimestampId(): string { + const now = new Date(); + + const pad = (num: number, length: number = 2): string => String(num).padStart(length, '0'); + + const datePart = `${now.getFullYear()}${pad(now.getMonth() + 1)}${pad(now.getDate())}`; + const timePart = `${pad(now.getHours())}${pad(now.getMinutes())}${pad(now.getSeconds())}`; + + return `${datePart}${timePart}`; +} + // waitForElement(selector: string, timeout = 2000): Promise { // return new Promise((resolve, reject) => { // const interval = setInterval(() => { @@ -1040,7 +1051,7 @@ export class SubstanceSsg4ManufactureFormComponent implements OnInit, AfterViewI } } - async exportStepView(document: Document): Promise { + async exportStepView(document: Document): Promise { const elementToConvert = document.querySelector('app-ssg4m-scheme-view') as HTMLElement; const clone = elementToConvert.cloneNode(true) as HTMLElement; const initialWidth = elementToConvert.offsetWidth; @@ -1092,13 +1103,13 @@ export class SubstanceSsg4ManufactureFormComponent implements OnInit, AfterViewI const dataUrl = await toSvg(clone, options); const downloadLink = document.createElement('a'); downloadLink.href = dataUrl; - downloadLink.download = `ssg4m_step_view_${Date.now().toString(36) + Math.random().toString(36).substring(2, 9)}.svg`; + downloadLink.download = `ssg4m_step_view_${this.generateTimestampId()}.svg`; document.body.appendChild(downloadLink); downloadLink.click(); document.body.removeChild(downloadLink); - const commaIndex = dataUrl.indexOf(','); - document.body.removeChild(container); - return dataUrl.slice(commaIndex + 1); + // const commaIndex = dataUrl.indexOf(','); + // document.body.removeChild(container); + // return dataUrl.slice(commaIndex + 1); } async submit(): Promise { @@ -1127,10 +1138,11 @@ export class SubstanceSsg4ManufactureFormComponent implements OnInit, AfterViewI this.ssg4mSyntheticPathway.sbmsnDataText = jsonValue; // Save SVG as Clob - this.ssg4mSyntheticPathway.sbmsnImage = document.querySelector("#scheme-viz-view").innerHTML; + // this.ssg4mSyntheticPathway.sbmsnImage = document.querySelector("#scheme-viz-view").innerHTML; - const encodedSvg = await this.exportStepView(document) - this.ssg4mSyntheticPathway.stepViewImage = decodeURIComponent(encodedSvg); + await this.exportStepView(document) + // const encodedSvg = await this.exportStepView(document) + // this.ssg4mSyntheticPathway.stepViewImage = decodeURIComponent(encodedSvg); // After submitting Save button, the UI waits for 8 seconds to see if it gets a response. // after 5 seconds it displays a warning on the top of the UI form. @@ -1208,7 +1220,7 @@ export class SubstanceSsg4ManufactureFormComponent implements OnInit, AfterViewI setTimeout(tempCallback(s),3000); }; - window['schemeUtil'].renderScheme(window['schemeUtil'].makeDisplayGraph(JSON.parse(ssgjs)), "#scheme-viz-view"); + // window['schemeUtil'].renderScheme(window['schemeUtil'].makeDisplayGraph(JSON.parse(ssgjs)), "#scheme-viz-view"); } From 1b7c459cb8d8c3e4905af22bcd90009951ca14cc Mon Sep 17 00:00:00 2001 From: Jaroslav Iha Date: Thu, 27 Nov 2025 09:39:54 +0100 Subject: [PATCH 149/408] remove scheme view --- ...tance-form-ssg4m-process-card.component.ts | 100 +++++++++--------- .../substance-ssg4m-form.component.ts | 34 +++--- 2 files changed, 67 insertions(+), 67 deletions(-) diff --git a/src/app/core/substance-ssg4m/ssg4m-process/substance-form-ssg4m-process-card.component.ts b/src/app/core/substance-ssg4m/ssg4m-process/substance-form-ssg4m-process-card.component.ts index 5cc399657..ba65f948a 100644 --- a/src/app/core/substance-ssg4m/ssg4m-process/substance-form-ssg4m-process-card.component.ts +++ b/src/app/core/substance-ssg4m/ssg4m-process/substance-form-ssg4m-process-card.component.ts @@ -55,56 +55,56 @@ export class SubstanceFormSsg4mProcessCardComponent extends SubstanceCardBaseFil this.overlayContainer = this.overlayContainerService.getContainerElement(); let loaded = false; - setInterval(() => { - if (window['schemeUtil'] && !loaded) { - loaded = true; - //setup viz stuff - //TODO: make more configurable and standardized - console.log("About to configure the scheme view"); - window['schemeUtil'].debug = false; - - window['schemeUtil'].maxContinuousSteps = 1; - window['schemeUtil'].maxTextLen = 30; - window['schemeUtil'].BREAK_GAP = 300; - window['schemeUtil'].maxTitleTextLen = 100; - - const url = `${(this.configService.configData && this.configService.configData.apiBaseUrl) || '/'}api/v1/`; - const httpp = this.http; - window['schemeUtil'].apiBaseURL = url; - - //allow resolution of svgs - window['schemeUtil'].urlResolver = (u, cb) => { - httpp.get(u, { responseType: 'text' }).subscribe(svg => { - cb(svg); - }, error => { - cb("ERROR"); - }); - }; - //TODO: - window['schemeUtil'].onClickReaction = (d) => { - //Can we add a popup dialog that would show the specific step here? - let pindex = d.processIndex; - let sindex = d.stepIndex; - let siteIndex = d.siteIndex; - if (typeof siteIndex === "undefined") { - siteIndex = 0; - } - this.showStepViewDialog(pindex, siteIndex, sindex); - - //I just want to show a dialog that shows the step/stage component rendered in a popup for now. - //maybe in the future it should instead be a side window, I don't know. - }; - - //TODO: - window['schemeUtil'].onClickMaterial = (d) => { - this.openImageModal(d.refuuid, d.name, d.bottomText); - }; - - if (window['schemeUtil'].executeWhenLoaded) { - window['schemeUtil'].executeWhenLoaded(); - } - } - }, 100); + // setInterval(() => { + // if (window['schemeUtil'] && !loaded) { + // loaded = true; + // //setup viz stuff + // //TODO: make more configurable and standardized + // console.log("About to configure the scheme view"); + // window['schemeUtil'].debug = false; + + // window['schemeUtil'].maxContinuousSteps = 1; + // window['schemeUtil'].maxTextLen = 30; + // window['schemeUtil'].BREAK_GAP = 300; + // window['schemeUtil'].maxTitleTextLen = 100; + + // const url = `${(this.configService.configData && this.configService.configData.apiBaseUrl) || '/'}api/v1/`; + // const httpp = this.http; + // window['schemeUtil'].apiBaseURL = url; + + // //allow resolution of svgs + // window['schemeUtil'].urlResolver = (u, cb) => { + // httpp.get(u, { responseType: 'text' }).subscribe(svg => { + // cb(svg); + // }, error => { + // cb("ERROR"); + // }); + // }; + // //TODO: + // window['schemeUtil'].onClickReaction = (d) => { + // //Can we add a popup dialog that would show the specific step here? + // let pindex = d.processIndex; + // let sindex = d.stepIndex; + // let siteIndex = d.siteIndex; + // if (typeof siteIndex === "undefined") { + // siteIndex = 0; + // } + // this.showStepViewDialog(pindex, siteIndex, sindex); + + // //I just want to show a dialog that shows the step/stage component rendered in a popup for now. + // //maybe in the future it should instead be a side window, I don't know. + // }; + + // //TODO: + // window['schemeUtil'].onClickMaterial = (d) => { + // this.openImageModal(d.refuuid, d.name, d.bottomText); + // }; + + // if (window['schemeUtil'].executeWhenLoaded) { + // window['schemeUtil'].executeWhenLoaded(); + // } + // } + // }, 100); // Get the parameter from URL and set the tab to either form view, step view, or scheme view. this.showView = this.activatedRoute.snapshot.queryParams['view'] || 'form'; diff --git a/src/app/core/substance-ssg4m/substance-ssg4m-form.component.ts b/src/app/core/substance-ssg4m/substance-ssg4m-form.component.ts index bcf30a31b..6d3cd8321 100644 --- a/src/app/core/substance-ssg4m/substance-ssg4m-form.component.ts +++ b/src/app/core/substance-ssg4m/substance-ssg4m-form.component.ts @@ -270,15 +270,15 @@ export class SubstanceSsg4ManufactureFormComponent implements OnInit, AfterViewI }); */ // Scheme View loading - if (!window['schemeUtil']) { - for (let i = 0; i < this.jsLibScriptUrls.length; i++) { - const node = document.createElement('script'); - node.src = this.jsLibScriptUrls[i]; - node.type = 'text/javascript'; - node.async = false; - document.getElementsByTagName('head')[0].appendChild(node); - } - } + // if (!window['schemeUtil']) { + // for (let i = 0; i < this.jsLibScriptUrls.length; i++) { + // const node = document.createElement('script'); + // node.src = this.jsLibScriptUrls[i]; + // node.type = 'text/javascript'; + // node.async = false; + // document.getElementsByTagName('head')[0].appendChild(node); + // } + // } } ngAfterViewInit(): void { @@ -1125,8 +1125,8 @@ export class SubstanceSsg4ManufactureFormComponent implements OnInit, AfterViewI //This is a hacky placeholder way to force viz //TODO finish this const ssgjs = JSON.stringify(this.substanceFormService.cleanSubstance()); - window["schemeUtil"].onFinishedLayout = async (svg) => { - window["schemeUtil"].onFinishedLayout = (svg) => { }; + // window["schemeUtil"].onFinishedLayout = async (svg) => { + // window["schemeUtil"].onFinishedLayout = (svg) => { }; // if New Record, initialize object if (this.ssg4mSyntheticPathway == null) { @@ -1211,14 +1211,14 @@ export class SubstanceSsg4ManufactureFormComponent implements OnInit, AfterViewI }); this.subscriptions.push(this.submitSubscription); - }; //window + // }; //window - let tempCallback = window["schemeUtil"].onFinishedLayout; - window["schemeUtil"].onFinishedLayout = (s)=>{ - window["schemeUtil"].onFinishedLayout =(ss)=>{}; + // let tempCallback = window["schemeUtil"].onFinishedLayout; + // window["schemeUtil"].onFinishedLayout = (s)=>{ + // window["schemeUtil"].onFinishedLayout =(ss)=>{}; - setTimeout(tempCallback(s),3000); - }; + // setTimeout(tempCallback(s),3000); + // }; // window['schemeUtil'].renderScheme(window['schemeUtil'].makeDisplayGraph(JSON.parse(ssgjs)), "#scheme-viz-view"); From 5ec70bf247d782abc7269f56e037a72078c3ebb2 Mon Sep 17 00:00:00 2001 From: Mitch Miller Date: Fri, 28 Nov 2025 15:12:34 -0500 Subject: [PATCH 150/408] allow substances to be located by any name not just the display name --- .../substance-selector.component.ts | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/app/core/substance-selector/substance-selector.component.ts b/src/app/core/substance-selector/substance-selector.component.ts index 504ff40f8..6bc442996 100644 --- a/src/app/core/substance-selector/substance-selector.component.ts +++ b/src/app/core/substance-selector/substance-selector.component.ts @@ -100,14 +100,14 @@ export class SubstanceSelectorComponent implements OnInit { const q = searchValue.replace('\"', ''); const searchStr = this.substanceSelectorProperties.map(property => `${property}:\"^${q}$\"`).join(' OR '); this.substanceService.getQuickSubstancesSummaries(searchStr, true).subscribe(response => { - if (response.content && response.content.length) { - let found = false; // Loop through the search results and compare the search term with search result's _name field for (let i = 0; i < response.content.length; i++) { let substance = response.content[i]; - if (substance._name && substance._name === searchValue) { + + this.substanceService.getSubstanceNames(substance.uuid).subscribe(names => { + if (names && names.some(n=>n.name === searchValue)) { // set to true, since search value matches with search result's _name field found = true; @@ -116,17 +116,16 @@ export class SubstanceSelectorComponent implements OnInit { this.selectionUpdated.emit(this.selectedSubstance); // break out of the loop when name found - break; - } + return; + } + }); } - // If Ingredient Name not found into the database, display message if (found == false) { this.errorMessage = 'No substances found'; } else { this.errorMessage = ''; } - } else { this.errorMessage = 'No substances found'; } From 6c860bf3a86f95996a93c73fcf951418f25ebfe3 Mon Sep 17 00:00:00 2001 From: Mitch Miller Date: Mon, 1 Dec 2025 20:03:30 -0500 Subject: [PATCH 151/408] resetting substance selector to the way it was implemented earlier --- .../substance-selector.component.ts | 39 ++++++++++++++++++- 1 file changed, 37 insertions(+), 2 deletions(-) diff --git a/src/app/core/substance-selector/substance-selector.component.ts b/src/app/core/substance-selector/substance-selector.component.ts index 6bc442996..4bb489393 100644 --- a/src/app/core/substance-selector/substance-selector.component.ts +++ b/src/app/core/substance-selector/substance-selector.component.ts @@ -99,12 +99,38 @@ export class SubstanceSelectorComponent implements OnInit { processSubstanceSearch(searchValue: string = ''): void { const q = searchValue.replace('\"', ''); const searchStr = this.substanceSelectorProperties.map(property => `${property}:\"^${q}$\"`).join(' OR '); + this.substanceService.getQuickSubstancesSummaries(searchStr, true).subscribe(response => { if (response.content && response.content.length) { + this.selectedSubstance = response.content[0]; + this.selectionUpdated.emit(this.selectedSubstance); + this.errorMessage = ''; + } else { + this.errorMessage = 'No substances found'; + } + }); + } + + processSubstanceSearchNew(searchValue: string = ''): void { + const q = searchValue.replace('\"', ''); + const searchStr = this.substanceSelectorProperties.map(property => `${property}:\"^${q}$\"`).join(' OR '); + console.log(`q: ${q}; searchStr: ${searchStr}`); + this.substanceService.getQuickSubstancesSummaries(searchStr, true).subscribe(response => { + console.log(`response: ${JSON.stringify(response)}`); + if (response.content && response.content.length) { + console.log('response has content'); let found = false; // Loop through the search results and compare the search term with search result's _name field for (let i = 0; i < response.content.length; i++) { let substance = response.content[i]; + if( (substance.uuid != null && substance.uuid === searchValue) || + (substance.approvalID !== null && substance.approvalID === searchValue)) { + found = true; + this.selectedSubstance = substance; + this.selectionUpdated.emit(this.selectedSubstance); + console.log('found match for UUID and/or approval ID'); + return; + } this.substanceService.getSubstanceNames(substance.uuid).subscribe(names => { if (names && names.some(n=>n.name === searchValue)) { @@ -114,11 +140,20 @@ export class SubstanceSelectorComponent implements OnInit { this.selectedSubstance = substance; this.selectionUpdated.emit(this.selectedSubstance); - + console.log('found match for name'); // break out of the loop when name found return; } - }); + }); + this.substanceService.getSubstanceCodes(substance.uuid).subscribe(codes => { + if(codes && codes.some(c=>c.code === searchValue)){ + found = true; + this.selectedSubstance = substance; + this.selectionUpdated.emit(this.selectedSubstance); + console.log('found match for code'); + return; + } + }); } // If Ingredient Name not found into the database, display message if (found == false) { From 6dac361949bd5b6f97fd2fcb5e3a86606652c5d5 Mon Sep 17 00:00:00 2001 From: Jaroslav Iha Date: Tue, 2 Dec 2025 14:29:59 +0100 Subject: [PATCH 152/408] fix delete export; disable step view export for pfda --- .../download-monitor/download-monitor.component.ts | 3 +++ .../core/substance-ssg4m/substance-ssg4m-form.component.ts | 5 +++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/app/core/auth/user-downloads/download-monitor/download-monitor.component.ts b/src/app/core/auth/user-downloads/download-monitor/download-monitor.component.ts index 2cc8343f6..1b807dde5 100644 --- a/src/app/core/auth/user-downloads/download-monitor/download-monitor.component.ts +++ b/src/app/core/auth/user-downloads/download-monitor/download-monitor.component.ts @@ -79,6 +79,9 @@ export class DownloadMonitorComponent implements OnInit, OnDestroy { } deleteDownload() { + if (!this.download.removeUrl.url) { + this.cancel(); + } this.authService.deleteDownload(this.download.removeUrl.url).pipe(take(1)).subscribe(response => { this.deleted = true; }); diff --git a/src/app/core/substance-ssg4m/substance-ssg4m-form.component.ts b/src/app/core/substance-ssg4m/substance-ssg4m-form.component.ts index 6d3cd8321..47bd8f8f3 100644 --- a/src/app/core/substance-ssg4m/substance-ssg4m-form.component.ts +++ b/src/app/core/substance-ssg4m/substance-ssg4m-form.component.ts @@ -1032,6 +1032,7 @@ export class SubstanceSsg4ManufactureFormComponent implements OnInit, AfterViewI // }); // } + // Step View cannot be hidden in order to export it async expandStepView(): Promise { const tabProcesses = document.querySelector("#substance-form-ssg4m-process") as HTMLElement; if (tabProcesses.getAttribute('aria-expanded') !== 'true') { @@ -1113,7 +1114,7 @@ export class SubstanceSsg4ManufactureFormComponent implements OnInit, AfterViewI } async submit(): Promise { - await this.expandStepView() + !this.configService.configData.isPfdaVersion && await this.expandStepView(); this.isLoading = true; this.loadingService.setLoading(true); this.approving = false; @@ -1140,7 +1141,7 @@ export class SubstanceSsg4ManufactureFormComponent implements OnInit, AfterViewI // Save SVG as Clob // this.ssg4mSyntheticPathway.sbmsnImage = document.querySelector("#scheme-viz-view").innerHTML; - await this.exportStepView(document) + !this.configService.configData.isPfdaVersion && await this.exportStepView(document) // const encodedSvg = await this.exportStepView(document) // this.ssg4mSyntheticPathway.stepViewImage = decodeURIComponent(encodedSvg); From 79cf6fdcdac0521a5718c03b7ed6c16ce5d16e88 Mon Sep 17 00:00:00 2001 From: Jaroslav Iha Date: Tue, 2 Dec 2025 15:23:02 +0100 Subject: [PATCH 153/408] fix delete export --- .../download-monitor/download-monitor.component.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/app/core/auth/user-downloads/download-monitor/download-monitor.component.ts b/src/app/core/auth/user-downloads/download-monitor/download-monitor.component.ts index 1b807dde5..eb36b0bca 100644 --- a/src/app/core/auth/user-downloads/download-monitor/download-monitor.component.ts +++ b/src/app/core/auth/user-downloads/download-monitor/download-monitor.component.ts @@ -79,8 +79,9 @@ export class DownloadMonitorComponent implements OnInit, OnDestroy { } deleteDownload() { - if (!this.download.removeUrl.url) { + if (!this.download.removeUrl || !this.download.removeUrl.url) { this.cancel(); + this.refresh(); } this.authService.deleteDownload(this.download.removeUrl.url).pipe(take(1)).subscribe(response => { this.deleted = true; From b69f4cffc7dcabb37cb55ab35bcaadccd72c0ea7 Mon Sep 17 00:00:00 2001 From: Jaroslav Iha Date: Tue, 2 Dec 2025 15:46:35 +0100 Subject: [PATCH 154/408] enable step view svg for pfda --- .../core/substance-ssg4m/substance-ssg4m-form.component.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/app/core/substance-ssg4m/substance-ssg4m-form.component.ts b/src/app/core/substance-ssg4m/substance-ssg4m-form.component.ts index 47bd8f8f3..6d3cd8321 100644 --- a/src/app/core/substance-ssg4m/substance-ssg4m-form.component.ts +++ b/src/app/core/substance-ssg4m/substance-ssg4m-form.component.ts @@ -1032,7 +1032,6 @@ export class SubstanceSsg4ManufactureFormComponent implements OnInit, AfterViewI // }); // } - // Step View cannot be hidden in order to export it async expandStepView(): Promise { const tabProcesses = document.querySelector("#substance-form-ssg4m-process") as HTMLElement; if (tabProcesses.getAttribute('aria-expanded') !== 'true') { @@ -1114,7 +1113,7 @@ export class SubstanceSsg4ManufactureFormComponent implements OnInit, AfterViewI } async submit(): Promise { - !this.configService.configData.isPfdaVersion && await this.expandStepView(); + await this.expandStepView() this.isLoading = true; this.loadingService.setLoading(true); this.approving = false; @@ -1141,7 +1140,7 @@ export class SubstanceSsg4ManufactureFormComponent implements OnInit, AfterViewI // Save SVG as Clob // this.ssg4mSyntheticPathway.sbmsnImage = document.querySelector("#scheme-viz-view").innerHTML; - !this.configService.configData.isPfdaVersion && await this.exportStepView(document) + await this.exportStepView(document) // const encodedSvg = await this.exportStepView(document) // this.ssg4mSyntheticPathway.stepViewImage = decodeURIComponent(encodedSvg); From 7b7c1d226f6ac43947440e1017ca98145b71dd6b Mon Sep 17 00:00:00 2001 From: Mitch Miller Date: Tue, 2 Dec 2025 16:52:56 -0500 Subject: [PATCH 155/408] reset the substance selector to the way it was before we started working on it. Fixed the config files to produce valid searches --- src/app/core/config/config.pfda.json | 3 +- .../substance-selector.component.ts | 59 +------------------ src/app/core/substance/substance.service.ts | 1 - src/app/fda/config/config.json | 4 +- 4 files changed, 7 insertions(+), 60 deletions(-) diff --git a/src/app/core/config/config.pfda.json b/src/app/core/config/config.pfda.json index dc35f4eb0..21c051f7c 100644 --- a/src/app/core/config/config.pfda.json +++ b/src/app/core/config/config.pfda.json @@ -530,7 +530,8 @@ "root_approvalID", "root_codes_BDNUM", "root_codes_CAS", - "root_codes_ECHA\\ \\(EC\/EINECS\\)" + "root_codes_ECHA", + "root_codes_EINECS" ], "contactEmail": "precisionfda-support@dnanexus.com", "sessionExpirationWarning": { diff --git a/src/app/core/substance-selector/substance-selector.component.ts b/src/app/core/substance-selector/substance-selector.component.ts index 4bb489393..7dfa40c76 100644 --- a/src/app/core/substance-selector/substance-selector.component.ts +++ b/src/app/core/substance-selector/substance-selector.component.ts @@ -101,6 +101,8 @@ export class SubstanceSelectorComponent implements OnInit { const searchStr = this.substanceSelectorProperties.map(property => `${property}:\"^${q}$\"`).join(' OR '); this.substanceService.getQuickSubstancesSummaries(searchStr, true).subscribe(response => { + console.log(`processSubstanceSearch response: ${JSON.stringify(response)}`); + response.total if (response.content && response.content.length) { this.selectedSubstance = response.content[0]; this.selectionUpdated.emit(this.selectedSubstance); @@ -111,63 +113,6 @@ export class SubstanceSelectorComponent implements OnInit { }); } - processSubstanceSearchNew(searchValue: string = ''): void { - const q = searchValue.replace('\"', ''); - const searchStr = this.substanceSelectorProperties.map(property => `${property}:\"^${q}$\"`).join(' OR '); - console.log(`q: ${q}; searchStr: ${searchStr}`); - this.substanceService.getQuickSubstancesSummaries(searchStr, true).subscribe(response => { - console.log(`response: ${JSON.stringify(response)}`); - if (response.content && response.content.length) { - console.log('response has content'); - let found = false; - // Loop through the search results and compare the search term with search result's _name field - for (let i = 0; i < response.content.length; i++) { - let substance = response.content[i]; - if( (substance.uuid != null && substance.uuid === searchValue) || - (substance.approvalID !== null && substance.approvalID === searchValue)) { - found = true; - this.selectedSubstance = substance; - this.selectionUpdated.emit(this.selectedSubstance); - console.log('found match for UUID and/or approval ID'); - return; - } - - this.substanceService.getSubstanceNames(substance.uuid).subscribe(names => { - if (names && names.some(n=>n.name === searchValue)) { - - // set to true, since search value matches with search result's _name field - found = true; - - this.selectedSubstance = substance; - this.selectionUpdated.emit(this.selectedSubstance); - console.log('found match for name'); - // break out of the loop when name found - return; - } - }); - this.substanceService.getSubstanceCodes(substance.uuid).subscribe(codes => { - if(codes && codes.some(c=>c.code === searchValue)){ - found = true; - this.selectedSubstance = substance; - this.selectionUpdated.emit(this.selectedSubstance); - console.log('found match for code'); - return; - } - }); - } - // If Ingredient Name not found into the database, display message - if (found == false) { - this.errorMessage = 'No substances found'; - } else { - this.errorMessage = ''; - } - } else { - this.errorMessage = 'No substances found'; - } - }); - } - - advanced(type: string): void { diff --git a/src/app/core/substance/substance.service.ts b/src/app/core/substance/substance.service.ts index 7ac1ae194..bec47b808 100644 --- a/src/app/core/substance/substance.service.ts +++ b/src/app/core/substance/substance.service.ts @@ -753,7 +753,6 @@ export class SubstanceService extends BaseHttpService { const options = { params: params }; - return this.http.get>(url, options); } diff --git a/src/app/fda/config/config.json b/src/app/fda/config/config.json index e1aa8b8ee..bc60deec5 100644 --- a/src/app/fda/config/config.json +++ b/src/app/fda/config/config.json @@ -1373,7 +1373,9 @@ "root_approvalID", "root_codes_BDNUM", "root_codes_CAS", - "root_codes_ECHA\\ \\(EC\\/EINECS\\)" + "root_codes_ECHA", + "root_codes_EC", + "root_codes_EINECS" ], "homeDynamicLinks": [ { From 8660c53468d891ff22f3a79ecff74600e9dd3d91 Mon Sep 17 00:00:00 2001 From: Jaroslav Iha Date: Wed, 3 Dec 2025 08:09:19 +0100 Subject: [PATCH 156/408] update export delete; --- .../download-monitor.component.ts | 35 ++++++++++++++----- 1 file changed, 26 insertions(+), 9 deletions(-) diff --git a/src/app/core/auth/user-downloads/download-monitor/download-monitor.component.ts b/src/app/core/auth/user-downloads/download-monitor/download-monitor.component.ts index eb36b0bca..e3e9c2a91 100644 --- a/src/app/core/auth/user-downloads/download-monitor/download-monitor.component.ts +++ b/src/app/core/auth/user-downloads/download-monitor/download-monitor.component.ts @@ -1,9 +1,10 @@ import { Component, OnInit, Input, Output, EventEmitter, OnDestroy } from '@angular/core'; import { AuthService } from '@gsrs-core/auth/auth.service'; import * as moment from 'moment'; -import { take } from 'rxjs/operators'; +import { switchMap, take, tap } from 'rxjs/operators'; import { ConfigService } from '@gsrs-core/config'; import { NavigationExtras } from '@angular/router'; +import { EMPTY, Observable, of } from 'rxjs'; @Component({ selector: 'app-download-monitor', @@ -67,9 +68,12 @@ export class DownloadMonitorComponent implements OnInit, OnDestroy { } cancel() { - this.authService.changeDownload(this.download.cancelUrl.url).pipe(take(1)).subscribe(response => { - this.refresh(); - }); + // Note: The cancel logic performs a changeDownload and then refreshes. + // We will now return the Observable so we can chain it. + return this.authService.changeDownload(this.download.cancelUrl.url).pipe( + take(1), + tap(() => this.refresh()) // Use tap to call refresh, but pass the Observable on + ); } downloadExport() { @@ -79,11 +83,24 @@ export class DownloadMonitorComponent implements OnInit, OnDestroy { } deleteDownload() { - if (!this.download.removeUrl || !this.download.removeUrl.url) { - this.cancel(); - this.refresh(); - } - this.authService.deleteDownload(this.download.removeUrl.url).pipe(take(1)).subscribe(response => { + // 1. Determine the URL to use for the initial action. + const action$: Observable = this.download.removeUrl?.url + ? of(null) // If remove URL exists, start with a resolved Observable (no initial action needed). + : this.cancel(); // If remove URL is missing, execute the cancel logic. + + action$.pipe( + // 2. Once the first action (cancel or no-op) is complete, switch to the deletion logic. + switchMap(() => { + // We only proceed to delete if the remove URL is defined. + if (this.download.removeUrl?.url) { + return this.authService.deleteDownload(this.download.removeUrl.url).pipe(take(1)); + } + + // If the remove URL was missing, we just ran 'cancel()' and stop here. + return EMPTY; + }) + ).subscribe(response => { + // 3. This runs only if the deletion was attempted and succeeded. this.deleted = true; }); } From ab881224c7eb32eadc18dc4e8c2d8bce46041cfc Mon Sep 17 00:00:00 2001 From: Jaroslav Iha Date: Wed, 3 Dec 2025 10:21:29 +0100 Subject: [PATCH 157/408] test fix delete download --- .../download-monitor.component.ts | 66 +++++++++++-------- 1 file changed, 38 insertions(+), 28 deletions(-) diff --git a/src/app/core/auth/user-downloads/download-monitor/download-monitor.component.ts b/src/app/core/auth/user-downloads/download-monitor/download-monitor.component.ts index e3e9c2a91..61fe96b6e 100644 --- a/src/app/core/auth/user-downloads/download-monitor/download-monitor.component.ts +++ b/src/app/core/auth/user-downloads/download-monitor/download-monitor.component.ts @@ -1,10 +1,9 @@ import { Component, OnInit, Input, Output, EventEmitter, OnDestroy } from '@angular/core'; import { AuthService } from '@gsrs-core/auth/auth.service'; import * as moment from 'moment'; -import { switchMap, take, tap } from 'rxjs/operators'; +import { take } from 'rxjs/operators'; import { ConfigService } from '@gsrs-core/config'; import { NavigationExtras } from '@angular/router'; -import { EMPTY, Observable, of } from 'rxjs'; @Component({ selector: 'app-download-monitor', @@ -68,12 +67,13 @@ export class DownloadMonitorComponent implements OnInit, OnDestroy { } cancel() { - // Note: The cancel logic performs a changeDownload and then refreshes. - // We will now return the Observable so we can chain it. - return this.authService.changeDownload(this.download.cancelUrl.url).pipe( - take(1), - tap(() => this.refresh()) // Use tap to call refresh, but pass the Observable on - ); + const obs = this.authService.changeDownload(this.download.cancelUrl.url).pipe(take(1)); + // keep existing behavior when called from template (subscribe and refresh) + obs.subscribe(response => { + this.refresh(); + }); + // also return the observable so callers can chain (used by deleteDownload) + return obs; } downloadExport() { @@ -83,26 +83,36 @@ export class DownloadMonitorComponent implements OnInit, OnDestroy { } deleteDownload() { - // 1. Determine the URL to use for the initial action. - const action$: Observable = this.download.removeUrl?.url - ? of(null) // If remove URL exists, start with a resolved Observable (no initial action needed). - : this.cancel(); // If remove URL is missing, execute the cancel logic. - - action$.pipe( - // 2. Once the first action (cancel or no-op) is complete, switch to the deletion logic. - switchMap(() => { - // We only proceed to delete if the remove URL is defined. - if (this.download.removeUrl?.url) { - return this.authService.deleteDownload(this.download.removeUrl.url).pipe(take(1)); - } - - // If the remove URL was missing, we just ran 'cancel()' and stop here. - return EMPTY; - }) - ).subscribe(response => { - // 3. This runs only if the deletion was attempted and succeeded. - this.deleted = true; - }); + if (this.download && this.download.removeUrl && this.download.removeUrl.url) { + this.authService.deleteDownload(this.download.removeUrl.url).pipe(take(1)).subscribe(response => { + this.deleted = true; + }); + } else { + // If there is no removeUrl yet, attempt to cancel the download first (which should create the removeUrl), + // then try deleting. If cancel or removeUrl are not available, mark as deleted to remove from view. + if (this.download && this.download.cancelUrl && this.download.cancelUrl.url) { + this.cancel().pipe(take(1)).subscribe(() => { + // After cancel completes, request the latest status to get any newly-created removeUrl + this.authService.getUpdateStatus(this.id).pipe(take(1)).subscribe(response => { + this.download = response; + if (this.download && this.download.removeUrl && this.download.removeUrl.url) { + this.authService.deleteDownload(this.download.removeUrl.url).pipe(take(1)).subscribe(resp => { + this.deleted = true; + }); + } else { + // fallback: if still no removeUrl, mark deleted to hide the entry + this.deleted = true; + } + }, err => { + // on error getting status, fallback to hiding the entry + this.deleted = true; + }); + }); + } else { + // No cancel URL either; nothing to call on server — hide it locally + this.deleted = true; + } + } } processQuery(url: string) { From 1c85259d2dc85fdb77e3aac4fbb193e380989746 Mon Sep 17 00:00:00 2001 From: Jaroslav Iha Date: Wed, 3 Dec 2025 12:07:23 +0100 Subject: [PATCH 158/408] fix delete download --- .../download-monitor.component.html | 9 ++++- .../download-monitor.component.ts | 39 ++----------------- 2 files changed, 12 insertions(+), 36 deletions(-) diff --git a/src/app/core/auth/user-downloads/download-monitor/download-monitor.component.html b/src/app/core/auth/user-downloads/download-monitor/download-monitor.component.html index 065e72d03..a968abb8e 100644 --- a/src/app/core/auth/user-downloads/download-monitor/download-monitor.component.html +++ b/src/app/core/auth/user-downloads/download-monitor/download-monitor.component.html @@ -126,7 +126,14 @@
    - diff --git a/src/app/core/auth/user-downloads/download-monitor/download-monitor.component.ts b/src/app/core/auth/user-downloads/download-monitor/download-monitor.component.ts index 61fe96b6e..2cc8343f6 100644 --- a/src/app/core/auth/user-downloads/download-monitor/download-monitor.component.ts +++ b/src/app/core/auth/user-downloads/download-monitor/download-monitor.component.ts @@ -67,13 +67,9 @@ export class DownloadMonitorComponent implements OnInit, OnDestroy { } cancel() { - const obs = this.authService.changeDownload(this.download.cancelUrl.url).pipe(take(1)); - // keep existing behavior when called from template (subscribe and refresh) - obs.subscribe(response => { + this.authService.changeDownload(this.download.cancelUrl.url).pipe(take(1)).subscribe(response => { this.refresh(); }); - // also return the observable so callers can chain (used by deleteDownload) - return obs; } downloadExport() { @@ -83,36 +79,9 @@ export class DownloadMonitorComponent implements OnInit, OnDestroy { } deleteDownload() { - if (this.download && this.download.removeUrl && this.download.removeUrl.url) { - this.authService.deleteDownload(this.download.removeUrl.url).pipe(take(1)).subscribe(response => { - this.deleted = true; - }); - } else { - // If there is no removeUrl yet, attempt to cancel the download first (which should create the removeUrl), - // then try deleting. If cancel or removeUrl are not available, mark as deleted to remove from view. - if (this.download && this.download.cancelUrl && this.download.cancelUrl.url) { - this.cancel().pipe(take(1)).subscribe(() => { - // After cancel completes, request the latest status to get any newly-created removeUrl - this.authService.getUpdateStatus(this.id).pipe(take(1)).subscribe(response => { - this.download = response; - if (this.download && this.download.removeUrl && this.download.removeUrl.url) { - this.authService.deleteDownload(this.download.removeUrl.url).pipe(take(1)).subscribe(resp => { - this.deleted = true; - }); - } else { - // fallback: if still no removeUrl, mark deleted to hide the entry - this.deleted = true; - } - }, err => { - // on error getting status, fallback to hiding the entry - this.deleted = true; - }); - }); - } else { - // No cancel URL either; nothing to call on server — hide it locally - this.deleted = true; - } - } + this.authService.deleteDownload(this.download.removeUrl.url).pipe(take(1)).subscribe(response => { + this.deleted = true; + }); } processQuery(url: string) { From 10971702e083ee9d395a112e019c154f2fa245c6 Mon Sep 17 00:00:00 2001 From: Jaroslav Iha Date: Wed, 3 Dec 2025 12:44:30 +0100 Subject: [PATCH 159/408] fix delete download --- .../download-monitor/download-monitor.component.html | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/src/app/core/auth/user-downloads/download-monitor/download-monitor.component.html b/src/app/core/auth/user-downloads/download-monitor/download-monitor.component.html index a968abb8e..065e72d03 100644 --- a/src/app/core/auth/user-downloads/download-monitor/download-monitor.component.html +++ b/src/app/core/auth/user-downloads/download-monitor/download-monitor.component.html @@ -126,14 +126,7 @@
    - From 691a73e9becc2824e5038e54a8827fcec79b3849 Mon Sep 17 00:00:00 2001 From: Jaroslav Iha Date: Wed, 3 Dec 2025 13:36:48 +0100 Subject: [PATCH 160/408] update enable delete button --- .../download-monitor/download-monitor.component.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/core/auth/user-downloads/download-monitor/download-monitor.component.html b/src/app/core/auth/user-downloads/download-monitor/download-monitor.component.html index 065e72d03..609ba4096 100644 --- a/src/app/core/auth/user-downloads/download-monitor/download-monitor.component.html +++ b/src/app/core/auth/user-downloads/download-monitor/download-monitor.component.html @@ -126,7 +126,7 @@
    - From 567f5c521691f5f229d491d69d5276135bd328cf Mon Sep 17 00:00:00 2001 From: Jaroslav Iha Date: Wed, 3 Dec 2025 14:10:22 +0100 Subject: [PATCH 161/408] try to disable button --- .../download-monitor/download-monitor.component.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/core/auth/user-downloads/download-monitor/download-monitor.component.html b/src/app/core/auth/user-downloads/download-monitor/download-monitor.component.html index 609ba4096..7fd9d1940 100644 --- a/src/app/core/auth/user-downloads/download-monitor/download-monitor.component.html +++ b/src/app/core/auth/user-downloads/download-monitor/download-monitor.component.html @@ -126,7 +126,7 @@

    - From a0a25bba9e21bdcc06ea2572cdab7c6a5ee79453 Mon Sep 17 00:00:00 2001 From: Jaroslav Iha Date: Wed, 3 Dec 2025 14:36:17 +0100 Subject: [PATCH 162/408] build remove url from cancel one --- .../download-monitor.component.ts | 45 +++++++++++++++++-- 1 file changed, 41 insertions(+), 4 deletions(-) diff --git a/src/app/core/auth/user-downloads/download-monitor/download-monitor.component.ts b/src/app/core/auth/user-downloads/download-monitor/download-monitor.component.ts index 2cc8343f6..9150f8f61 100644 --- a/src/app/core/auth/user-downloads/download-monitor/download-monitor.component.ts +++ b/src/app/core/auth/user-downloads/download-monitor/download-monitor.component.ts @@ -67,9 +67,13 @@ export class DownloadMonitorComponent implements OnInit, OnDestroy { } cancel() { - this.authService.changeDownload(this.download.cancelUrl.url).pipe(take(1)).subscribe(response => { + const obs = this.authService.changeDownload(this.download.cancelUrl.url).pipe(take(1)); + // keep existing behavior when called from template (subscribe and refresh) + obs.subscribe(response => { this.refresh(); }); + // also return the observable so callers can chain (used by deleteDownload) + return obs; } downloadExport() { @@ -79,9 +83,42 @@ export class DownloadMonitorComponent implements OnInit, OnDestroy { } deleteDownload() { - this.authService.deleteDownload(this.download.removeUrl.url).pipe(take(1)).subscribe(response => { - this.deleted = true; - }); + if (this.download && this.download.removeUrl && this.download.removeUrl.url) { + this.authService.deleteDownload(this.download.removeUrl.url).pipe(take(1)).subscribe(response => { + this.deleted = true; + }); + } else { + // If there is no removeUrl yet, attempt to cancel the download first (which should create the removeUrl), + // then try deleting. If cancel or removeUrl are not available, mark as deleted to remove from view. + if (this.download && this.download.cancelUrl && this.download.cancelUrl.url) { + this.cancel().pipe(take(1)).subscribe(() => { + // After cancel completes, request the latest status to get any newly-created removeUrl + this.authService.getUpdateStatus(this.id).pipe(take(1)).subscribe(response => { + this.download = response; + if (this.download && this.download.removeUrl && this.download.removeUrl.url) { + this.authService.deleteDownload(this.download.removeUrl.url).pipe(take(1)).subscribe(resp => { + this.deleted = true; + }); + } else { + // fallback: if still no removeUrl, mark deleted to hide the entry + if (this.download.cancelUrl?.url) { + this.authService.deleteDownload(this.download.cancelUrl.url.replace('/@cancel', '')).pipe(take(1)).subscribe(resp => { + this.deleted = true; + }); + } else { + this.deleted = true; + } + } + }, err => { + // on error getting status, fallback to hiding the entry + this.deleted = true; + }); + }); + } else { + // No cancel URL either; nothing to call on server — hide it locally + this.deleted = true; + } + } } processQuery(url: string) { From e792968f268245770c43d33da32b554124a6e381 Mon Sep 17 00:00:00 2001 From: Jaroslav Iha Date: Wed, 3 Dec 2025 15:03:30 +0100 Subject: [PATCH 163/408] revert button disable --- .../download-monitor/download-monitor.component.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/core/auth/user-downloads/download-monitor/download-monitor.component.html b/src/app/core/auth/user-downloads/download-monitor/download-monitor.component.html index 7fd9d1940..065e72d03 100644 --- a/src/app/core/auth/user-downloads/download-monitor/download-monitor.component.html +++ b/src/app/core/auth/user-downloads/download-monitor/download-monitor.component.html @@ -126,7 +126,7 @@
    - From d549b413bdb1d1f3853e8c8e4e7f094938a4e8a4 Mon Sep 17 00:00:00 2001 From: Jaroslav Iha Date: Wed, 3 Dec 2025 15:10:19 +0100 Subject: [PATCH 164/408] clean delete fix code --- .../download-monitor/download-monitor.component.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/app/core/auth/user-downloads/download-monitor/download-monitor.component.ts b/src/app/core/auth/user-downloads/download-monitor/download-monitor.component.ts index 9150f8f61..29fe6dc8a 100644 --- a/src/app/core/auth/user-downloads/download-monitor/download-monitor.component.ts +++ b/src/app/core/auth/user-downloads/download-monitor/download-monitor.component.ts @@ -89,7 +89,7 @@ export class DownloadMonitorComponent implements OnInit, OnDestroy { }); } else { // If there is no removeUrl yet, attempt to cancel the download first (which should create the removeUrl), - // then try deleting. If cancel or removeUrl are not available, mark as deleted to remove from view. + // then try deleting. If cancel or removeUrl are not available, build the remove url from cancel one, otherwise mark as deleted to remove from view. if (this.download && this.download.cancelUrl && this.download.cancelUrl.url) { this.cancel().pipe(take(1)).subscribe(() => { // After cancel completes, request the latest status to get any newly-created removeUrl @@ -100,7 +100,7 @@ export class DownloadMonitorComponent implements OnInit, OnDestroy { this.deleted = true; }); } else { - // fallback: if still no removeUrl, mark deleted to hide the entry + // fallback: if still no removeUrl, try to create it from cancelUrl if (this.download.cancelUrl?.url) { this.authService.deleteDownload(this.download.cancelUrl.url.replace('/@cancel', '')).pipe(take(1)).subscribe(resp => { this.deleted = true; From d3b90b9e39ace994c2271778b405651b7bb5b149 Mon Sep 17 00:00:00 2001 From: Jaroslav Iha Date: Wed, 3 Dec 2025 15:29:29 +0100 Subject: [PATCH 165/408] first test of add Draft to g4ssm --- .../ssg4m-stages-form.component.html | 6 + .../ssg4m-stages-form.component.ts | 283 ++++++++++++++---- 2 files changed, 232 insertions(+), 57 deletions(-) diff --git a/src/app/core/substance-ssg4m/ssg4m-stages/ssg4m-stages-form.component.html b/src/app/core/substance-ssg4m/ssg4m-stages/ssg4m-stages-form.component.html index b6b891b16..3bb62749b 100644 --- a/src/app/core/substance-ssg4m/ssg4m-stages/ssg4m-stages-form.component.html +++ b/src/app/core/substance-ssg4m/ssg4m-stages/ssg4m-stages-form.component.html @@ -100,6 +100,12 @@ (click)="addStartingMaterial(processIndex, siteIndex, stageIndex)"> Add Input Material + +
    diff --git a/src/app/core/substance-ssg4m/ssg4m-stages/ssg4m-stages-form.component.ts b/src/app/core/substance-ssg4m/ssg4m-stages/ssg4m-stages-form.component.ts index 1ac2de87c..b13189cc3 100644 --- a/src/app/core/substance-ssg4m/ssg4m-stages/ssg4m-stages-form.component.ts +++ b/src/app/core/substance-ssg4m/ssg4m-stages/ssg4m-stages-form.component.ts @@ -1,12 +1,21 @@ -import { Component, OnInit, OnDestroy, AfterViewInit, Input } from '@angular/core'; -import { MatDialog } from '@angular/material/dialog'; -import { OverlayContainer } from '@angular/cdk/overlay'; -import { ScrollToService } from '../../scroll-to/scroll-to.service'; -import { Subscription } from 'rxjs'; -import { GoogleAnalyticsService } from '../../google-analytics/google-analytics.service'; -import { ConfigService } from '@gsrs-core/config/config.service'; -import { SubstanceCardBaseFilteredList, SubstanceCardBaseList } from '../../substance-form/base-classes/substance-form-base-filtered-list'; -import { SubstanceFormService } from '../../substance-form/substance-form.service'; +import { + Component, + OnInit, + OnDestroy, + AfterViewInit, + Input, +} from "@angular/core"; +import { MatDialog } from "@angular/material/dialog"; +import { OverlayContainer } from "@angular/cdk/overlay"; +import { ScrollToService } from "../../scroll-to/scroll-to.service"; +import { Subscription } from "rxjs"; +import { GoogleAnalyticsService } from "../../google-analytics/google-analytics.service"; +import { ConfigService } from "@gsrs-core/config/config.service"; +import { + SubstanceCardBaseFilteredList, + SubstanceCardBaseList, +} from "../../substance-form/base-classes/substance-form-base-filtered-list"; +import { SubstanceFormService } from "../../substance-form/substance-form.service"; /* import { take } from 'rxjs/operators'; import { ConfigService } from '@gsrs-core/config'; @@ -14,21 +23,29 @@ import { SubstanceFormBase } from '../../substance-form/base-classes/substance-f import { ControlledVocabularyService } from '../../controlled-vocabulary/controlled-vocabulary.service'; import { VocabularyTerm } from '../../controlled-vocabulary/vocabulary.model'; */ -import { SubstanceService } from '../../substance/substance.service'; -import { SubstanceSummary, SubstanceRelationship } from '../../substance/substance.model'; -import { SpecifiedSubstanceG4mProcess, SubstanceRelated } from '../../substance/substance.model'; -import { SubstanceDetail } from '@gsrs-core/substance/substance.model'; -import { SubstanceFormSsg4mStagesService } from './substance-form-ssg4m-stages.service'; -import { SpecifiedSubstanceG4mStage } from '@gsrs-core/substance/substance.model'; -import { ConfirmDialogComponent } from '../../../fda/confirm-dialog/confirm-dialog.component'; +import { SubstanceService } from "../../substance/substance.service"; +import { + SubstanceSummary, + SubstanceRelationship, +} from "../../substance/substance.model"; +import { + SpecifiedSubstanceG4mProcess, + SubstanceRelated, +} from "../../substance/substance.model"; +import { SubstanceDetail } from "@gsrs-core/substance/substance.model"; +import { SubstanceFormSsg4mStagesService } from "./substance-form-ssg4m-stages.service"; +import { SubstanceDraftsComponent } from "@gsrs-core/substance-form/substance-drafts/substance-drafts.component"; +import { SpecifiedSubstanceG4mStage } from "@gsrs-core/substance/substance.model"; +import { ConfirmDialogComponent } from "../../../fda/confirm-dialog/confirm-dialog.component"; @Component({ - selector: 'app-ssg4m-stages-form', - templateUrl: './ssg4m-stages-form.component.html', - styleUrls: ['./ssg4m-stages-form.component.scss'] + selector: "app-ssg4m-stages-form", + templateUrl: "./ssg4m-stages-form.component.html", + styleUrls: ["./ssg4m-stages-form.component.scss"], }) export class Ssg4mStagesFormComponent implements OnInit, OnDestroy { public configSettingsDisplay = {}; + private overlayContainer: HTMLElement; configSsg4Form: any; configTitleStage: string; configTitleProcessingMaterials: string; @@ -49,7 +66,7 @@ export class Ssg4mStagesFormComponent implements OnInit, OnDestroy { private scrollToService: ScrollToService, public configService: ConfigService, private dialog: MatDialog - ) { } + ) {} @Input() set stage(stage: SpecifiedSubstanceG4mStage) { @@ -82,7 +99,7 @@ export class Ssg4mStagesFormComponent implements OnInit, OnDestroy { set stageIndex(stageIndex: number) { this.privateStageIndex = stageIndex; // Set the Stage Name - // alert("STAGE INDEX: " + stageIndex); + // alert("STAGE INDEX: " + stageIndex); this.privateStage.stageNumber = String(this.privateStageIndex + 1); } @@ -112,30 +129,39 @@ export class Ssg4mStagesFormComponent implements OnInit, OnDestroy { ngOnInit(): void { // this.substance = this.substanceFormSsg4mStagesService.substance; - const subscription = this.substanceFormService.substance.subscribe(substance => { - this.substance = substance; - }); + const subscription = this.substanceFormService.substance.subscribe( + (substance) => { + this.substance = substance; + } + ); this.subscriptions.push(subscription); + // overlay container for dialogs + this.overlayContainer = this.overlayContainerService.getContainerElement(); + // Get Config variables for SSG4m - this.configSsg4Form = (this.configService.configData && this.configService.configData.ssg4Form) || null; - this.configTitleStage = 'Stage'; + this.configSsg4Form = + (this.configService.configData && + this.configService.configData.ssg4Form) || + null; + this.configTitleStage = "Stage"; this.configTitleProcessingMaterials = "Processing Materials"; if (this.configSsg4Form) { this.configTitleStage = this.configSsg4Form.titles.stage || null; if (!this.configTitleStage) { - this.configTitleStage = 'Stage'; + this.configTitleStage = "Stage"; } - this.configTitleProcessingMaterials = this.configSsg4Form.titles.processingMaterials || null; + this.configTitleProcessingMaterials = + this.configSsg4Form.titles.processingMaterials || null; if (!this.configTitleProcessingMaterials) { - this.configTitleProcessingMaterials = 'Processing Materials'; + this.configTitleProcessingMaterials = "Processing Materials"; } } } ngOnDestroy(): void { // this.substanceFormService.unloadSubstance(); - this.subscriptions.forEach(subscription => { + this.subscriptions.forEach((subscription) => { subscription.unsubscribe(); }); } @@ -143,74 +169,155 @@ export class Ssg4mStagesFormComponent implements OnInit, OnDestroy { getConfigSettings(): void { // Get SSG4 Config Settings from config.json file to show and hide fields in the form let configSsg4Form: any; - configSsg4Form = this.configService.configData && this.configService.configData.ssg4Form || null; + configSsg4Form = + (this.configService.configData && + this.configService.configData.ssg4Form) || + null; // Get 'stage' json values from config const confSettings = configSsg4Form.settingsDisplay.stage; - Object.keys(confSettings).forEach(key => { + Object.keys(confSettings).forEach((key) => { if (confSettings[key] != null) { - if (confSettings[key] === 'simple') { + if (confSettings[key] === "simple") { this.configSettingsDisplay[key] = true; - } else if (confSettings[key] === 'advanced') { + } else if (confSettings[key] === "advanced") { if (this.privateShowAdvancedSettings === true) { this.configSettingsDisplay[key] = true; } else { this.configSettingsDisplay[key] = false; } - } else if (confSettings[key] === 'removed') { + } else if (confSettings[key] === "removed") { this.configSettingsDisplay[key] = false; } } }); } - insertStage(processIndex: number, siteIndex: number, stageIndex: number, insertDirection?: string): void { - this.substanceFormSsg4mStagesService.insertStage(processIndex, siteIndex, stageIndex, insertDirection); + insertStage( + processIndex: number, + siteIndex: number, + stageIndex: number, + insertDirection?: string + ): void { + this.substanceFormSsg4mStagesService.insertStage( + processIndex, + siteIndex, + stageIndex, + insertDirection + ); setTimeout(() => { - this.scrollToService.scrollToElement(`substance-process-0`, 'center'); + this.scrollToService.scrollToElement(`substance-process-0`, "center"); }); } - duplicateStage(processIndex: number, siteIndex: number, stageIndex: number, insertDirection?: string): void { - this.substanceFormSsg4mStagesService.duplicateStage(processIndex, siteIndex, stageIndex, insertDirection); + duplicateStage( + processIndex: number, + siteIndex: number, + stageIndex: number, + insertDirection?: string + ): void { + this.substanceFormSsg4mStagesService.duplicateStage( + processIndex, + siteIndex, + stageIndex, + insertDirection + ); setTimeout(() => { - this.scrollToService.scrollToElement(`substance-stage-duplicate-0`, 'center'); + this.scrollToService.scrollToElement( + `substance-stage-duplicate-0`, + "center" + ); }); } - addCriticalParameter(processIndex: number, siteIndex: number, stageIndex: number) { - this.substanceFormSsg4mStagesService.addCriticalParameter(processIndex, siteIndex, stageIndex); + addCriticalParameter( + processIndex: number, + siteIndex: number, + stageIndex: number + ) { + this.substanceFormSsg4mStagesService.addCriticalParameter( + processIndex, + siteIndex, + stageIndex + ); setTimeout(() => { - this.scrollToService.scrollToElement(`substance-process-site-stage-criticalParam-0`, 'center'); + this.scrollToService.scrollToElement( + `substance-process-site-stage-criticalParam-0`, + "center" + ); }); } - addStartingMaterial(processIndex: number, siteIndex: number, stageIndex: number) { - this.substanceFormSsg4mStagesService.addStartingMaterials(processIndex, siteIndex, stageIndex); + addStartingMaterial( + processIndex: number, + siteIndex: number, + stageIndex: number + ) { + this.substanceFormSsg4mStagesService.addStartingMaterials( + processIndex, + siteIndex, + stageIndex + ); setTimeout(() => { - this.scrollToService.scrollToElement(`substance-process-site-stage-startMat-0`, 'center'); + this.scrollToService.scrollToElement( + `substance-process-site-stage-startMat-0`, + "center" + ); }); } - addProcessingMaterial(processIndex: number, siteIndex: number, stageIndex: number) { - this.substanceFormSsg4mStagesService.addProcessingMaterials(processIndex, siteIndex, stageIndex); + addProcessingMaterial( + processIndex: number, + siteIndex: number, + stageIndex: number + ) { + this.substanceFormSsg4mStagesService.addProcessingMaterials( + processIndex, + siteIndex, + stageIndex + ); setTimeout(() => { - this.scrollToService.scrollToElement(`substance-process-site-stage-processMat-0`, 'center'); + this.scrollToService.scrollToElement( + `substance-process-site-stage-processMat-0`, + "center" + ); }); } - addResultingMaterial(processIndex: number, siteIndex: number, stageIndex: number) { - this.substanceFormSsg4mStagesService.addResultingMaterials(processIndex, siteIndex, stageIndex); + addResultingMaterial( + processIndex: number, + siteIndex: number, + stageIndex: number + ) { + this.substanceFormSsg4mStagesService.addResultingMaterials( + processIndex, + siteIndex, + stageIndex + ); setTimeout(() => { - this.scrollToService.scrollToElement(`substance-process-site-stage-resultMat-0`, 'center'); + this.scrollToService.scrollToElement( + `substance-process-site-stage-resultMat-0`, + "center" + ); }); } confirmDeleteStage() { const dialogRef = this.dialog.open(ConfirmDialogComponent, { - data: { message: 'Are you sure you want to delele ' + this.configTitleStage + ' ' + (this.stageIndex + 1) + ' for Site ' + (this.siteIndex + 1) + ' for Process ' + (this.processIndex + 1) + '?' } + data: { + message: + "Are you sure you want to delele " + + this.configTitleStage + + " " + + (this.stageIndex + 1) + + " for Site " + + (this.siteIndex + 1) + + " for Process " + + (this.processIndex + 1) + + "?", + }, }); - dialogRef.afterClosed().subscribe(result => { + dialogRef.afterClosed().subscribe((result) => { if (result && result === true) { this.deleteStage(); } @@ -218,7 +325,70 @@ export class Ssg4mStagesFormComponent implements OnInit, OnDestroy { } deleteStage(): void { - this.substance.specifiedSubstanceG4m.process[this.processIndex].sites[this.siteIndex].stages.splice(this.stageIndex, 1); + this.substance.specifiedSubstanceG4m.process[this.processIndex].sites[ + this.siteIndex + ].stages.splice(this.stageIndex, 1); + } + + /** + * Open drafts dialog and add selected draft as a Starting Material for this stage + */ + addStartingMaterialFromDraft( + processIndex: number, + siteIndex: number, + stageIndex: number + ) { + const dialogRef = this.dialog.open(SubstanceDraftsComponent, { + maxHeight: "85%", + width: "70%", + data: { uuid: this.substance ? this.substance.uuid : null }, + }); + if (this.overlayContainer) { + this.overlayContainer.style.zIndex = "1002"; + } + + const sub = dialogRef.afterClosed().subscribe((response) => { + if (this.overlayContainer) { + this.overlayContainer.style.zIndex = null; + } + if (response && response.substance) { + const read = response.substance; + + // Add a new starting material then set its substanceName to reference the selected draft + this.substanceFormSsg4mStagesService.addStartingMaterials( + processIndex, + siteIndex, + stageIndex + ); + + const stageObj = + this.substance.specifiedSubstanceG4m.process[processIndex].sites[ + siteIndex + ].stages[stageIndex]; + const newStartIndex = stageObj.startingMaterials.length - 1; + + // Determine a display name for the substance + let displayName = read._name + ? String(read._name).replace(/<[^>]*>?/gm, "") + : null; + if (!displayName && read.names && read.names.length > 0) { + const n = read.names.find((x) => x.stdName) || read.names[0]; + displayName = n.stdName || n.name || null; + } + + stageObj.startingMaterials[newStartIndex].substanceName = { + refuuid: read.uuid, + name: displayName, + substanceClass: read.substanceClass, + } as any; + + // notify subscribers about the change + this.substanceFormSsg4mStagesService.propertyEmitter.next( + stageObj.startingMaterials + ); + } + sub.unsubscribe(); + }); } /* @@ -230,4 +400,3 @@ export class Ssg4mStagesFormComponent implements OnInit, OnDestroy { } */ } - From 6f236d83abb4a491687e525c65ea2ee150481699 Mon Sep 17 00:00:00 2001 From: Mitch Miller Date: Wed, 3 Dec 2025 15:53:07 -0500 Subject: [PATCH 166/408] reintroduce handling of UUIDs in substance selector --- .../substance-selector.component.ts | 24 ++++++++++++++++--- src/app/core/substance/substance.service.ts | 16 +++++++------ 2 files changed, 30 insertions(+), 10 deletions(-) diff --git a/src/app/core/substance-selector/substance-selector.component.ts b/src/app/core/substance-selector/substance-selector.component.ts index 7dfa40c76..c5ed4781f 100644 --- a/src/app/core/substance-selector/substance-selector.component.ts +++ b/src/app/core/substance-selector/substance-selector.component.ts @@ -98,11 +98,29 @@ export class SubstanceSelectorComponent implements OnInit { processSubstanceSearch(searchValue: string = ''): void { const q = searchValue.replace('\"', ''); + if( this.substanceService.isUUID(q)) { + console.log('detected a UUID') + this.substanceService.getSubstanceDetails(q).subscribe( { + next: response => { + if(response && response != null) { + this.selectedSubstance = response; + this.selectionUpdated.emit(this.selectedSubstance); + this.errorMessage = ''; + console.log('got substance via UUID'); + } else { + console.log('no match for UUID'); + this.errorMessage = 'No substances found'; + } + }, + error: err=>{ + console.log('error retrieving UUID'); + this.errorMessage = 'No substances found'; + } + }); + return; + } const searchStr = this.substanceSelectorProperties.map(property => `${property}:\"^${q}$\"`).join(' OR '); - this.substanceService.getQuickSubstancesSummaries(searchStr, true).subscribe(response => { - console.log(`processSubstanceSearch response: ${JSON.stringify(response)}`); - response.total if (response.content && response.content.length) { this.selectedSubstance = response.content[0]; this.selectionUpdated.emit(this.selectedSubstance); diff --git a/src/app/core/substance/substance.service.ts b/src/app/core/substance/substance.service.ts index bec47b808..cbf7d0118 100644 --- a/src/app/core/substance/substance.service.ts +++ b/src/app/core/substance/substance.service.ts @@ -753,9 +753,17 @@ export class SubstanceService extends BaseHttpService { const options = { params: params }; + console.log(`url: ${url} parms ${options.params}`); return this.http.get>(url, options); } + isUUID(uuidCandidate) { + if ((uuidCandidate + "").match(/^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/)) { + return true; + } + return false; + } + getAllByEtag(etag: string) { let url = this.apiBaseUrl + 'substances/search?top=1000000&etag=' + etag; return this.http.get(url); @@ -1153,10 +1161,4 @@ export class SubstanceService extends BaseHttpService { public GetSubstanceOldValue(url:string) { return this.http.get(url); } -} - - - - - - +} \ No newline at end of file From fd7a43c83b8442b9e907247100bb453647efb2d8 Mon Sep 17 00:00:00 2001 From: Mitch Miller Date: Wed, 3 Dec 2025 16:42:12 -0500 Subject: [PATCH 167/408] updated config --- src/app/core/config/config.pfda.json | 3 +-- src/app/fda/config/config.json | 4 +--- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/src/app/core/config/config.pfda.json b/src/app/core/config/config.pfda.json index 21c051f7c..cd265bc04 100644 --- a/src/app/core/config/config.pfda.json +++ b/src/app/core/config/config.pfda.json @@ -530,8 +530,7 @@ "root_approvalID", "root_codes_BDNUM", "root_codes_CAS", - "root_codes_ECHA", - "root_codes_EINECS" + "root_codes_ECHA" ], "contactEmail": "precisionfda-support@dnanexus.com", "sessionExpirationWarning": { diff --git a/src/app/fda/config/config.json b/src/app/fda/config/config.json index bc60deec5..b3863977f 100644 --- a/src/app/fda/config/config.json +++ b/src/app/fda/config/config.json @@ -1373,9 +1373,7 @@ "root_approvalID", "root_codes_BDNUM", "root_codes_CAS", - "root_codes_ECHA", - "root_codes_EC", - "root_codes_EINECS" + "root_codes_ECHA" ], "homeDynamicLinks": [ { From b248170eb1bca071f4d669f6a42f74aff37b4580 Mon Sep 17 00:00:00 2001 From: Jaroslav Iha Date: Mon, 8 Dec 2025 14:29:48 +0100 Subject: [PATCH 168/408] displayname fix --- .../ssg4m-stages/ssg4m-stages-form.component.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/app/core/substance-ssg4m/ssg4m-stages/ssg4m-stages-form.component.ts b/src/app/core/substance-ssg4m/ssg4m-stages/ssg4m-stages-form.component.ts index b13189cc3..f80ae130a 100644 --- a/src/app/core/substance-ssg4m/ssg4m-stages/ssg4m-stages-form.component.ts +++ b/src/app/core/substance-ssg4m/ssg4m-stages/ssg4m-stages-form.component.ts @@ -368,8 +368,8 @@ export class Ssg4mStagesFormComponent implements OnInit, OnDestroy { const newStartIndex = stageObj.startingMaterials.length - 1; // Determine a display name for the substance - let displayName = read._name - ? String(read._name).replace(/<[^>]*>?/gm, "") + let displayName = response.name + ? String(response.name).replace(/<[^>]*>?/gm, "") : null; if (!displayName && read.names && read.names.length > 0) { const n = read.names.find((x) => x.stdName) || read.names[0]; From ee9340a852005ef9d9c7d5d0b9b8fcd5b0a9b256 Mon Sep 17 00:00:00 2001 From: Mitch Miller Date: Fri, 12 Dec 2025 21:54:34 -0500 Subject: [PATCH 169/408] updated version to 3.1.4-SNAPSHOT --- src/app/core/config/config.json | 2 +- src/app/fda/config/config.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/app/core/config/config.json b/src/app/core/config/config.json index ac54c92f8..e05b0d9a0 100644 --- a/src/app/core/config/config.json +++ b/src/app/core/config/config.json @@ -1,5 +1,5 @@ { - "version": "3.0.3", + "version": "3.1.4-SNAPSHOT", "contactEmail": "ncatsgsrs@mail.nih.gov", "displayMatchApplication": "false", "adverseEventShinyHomepageDisplay": "false", diff --git a/src/app/fda/config/config.json b/src/app/fda/config/config.json index a16f483b3..307ffa6a9 100644 --- a/src/app/fda/config/config.json +++ b/src/app/fda/config/config.json @@ -1,5 +1,5 @@ { - "version": "3.1.3-SNAPSHOT", + "version": "3.1.4-SNAPSHOT", "contactEmail": "GSRSSupport@fda.hhs.gov", "displayMatchApplication": "true", "adverseEventShinyHomepageDisplay": "true", From a1715797216e30b6f405a43733afb9e989e18911 Mon Sep 17 00:00:00 2001 From: Jaroslav Iha Date: Tue, 16 Dec 2025 10:57:37 +0100 Subject: [PATCH 170/408] revert g4ssm add draft changes --- .../ssg4m-stages-form.component.html | 6 - .../ssg4m-stages-form.component.ts | 283 ++++-------------- 2 files changed, 57 insertions(+), 232 deletions(-) diff --git a/src/app/core/substance-ssg4m/ssg4m-stages/ssg4m-stages-form.component.html b/src/app/core/substance-ssg4m/ssg4m-stages/ssg4m-stages-form.component.html index 3bb62749b..b6b891b16 100644 --- a/src/app/core/substance-ssg4m/ssg4m-stages/ssg4m-stages-form.component.html +++ b/src/app/core/substance-ssg4m/ssg4m-stages/ssg4m-stages-form.component.html @@ -100,12 +100,6 @@ (click)="addStartingMaterial(processIndex, siteIndex, stageIndex)"> Add Input Material - -
    diff --git a/src/app/core/substance-ssg4m/ssg4m-stages/ssg4m-stages-form.component.ts b/src/app/core/substance-ssg4m/ssg4m-stages/ssg4m-stages-form.component.ts index f80ae130a..1ac2de87c 100644 --- a/src/app/core/substance-ssg4m/ssg4m-stages/ssg4m-stages-form.component.ts +++ b/src/app/core/substance-ssg4m/ssg4m-stages/ssg4m-stages-form.component.ts @@ -1,21 +1,12 @@ -import { - Component, - OnInit, - OnDestroy, - AfterViewInit, - Input, -} from "@angular/core"; -import { MatDialog } from "@angular/material/dialog"; -import { OverlayContainer } from "@angular/cdk/overlay"; -import { ScrollToService } from "../../scroll-to/scroll-to.service"; -import { Subscription } from "rxjs"; -import { GoogleAnalyticsService } from "../../google-analytics/google-analytics.service"; -import { ConfigService } from "@gsrs-core/config/config.service"; -import { - SubstanceCardBaseFilteredList, - SubstanceCardBaseList, -} from "../../substance-form/base-classes/substance-form-base-filtered-list"; -import { SubstanceFormService } from "../../substance-form/substance-form.service"; +import { Component, OnInit, OnDestroy, AfterViewInit, Input } from '@angular/core'; +import { MatDialog } from '@angular/material/dialog'; +import { OverlayContainer } from '@angular/cdk/overlay'; +import { ScrollToService } from '../../scroll-to/scroll-to.service'; +import { Subscription } from 'rxjs'; +import { GoogleAnalyticsService } from '../../google-analytics/google-analytics.service'; +import { ConfigService } from '@gsrs-core/config/config.service'; +import { SubstanceCardBaseFilteredList, SubstanceCardBaseList } from '../../substance-form/base-classes/substance-form-base-filtered-list'; +import { SubstanceFormService } from '../../substance-form/substance-form.service'; /* import { take } from 'rxjs/operators'; import { ConfigService } from '@gsrs-core/config'; @@ -23,29 +14,21 @@ import { SubstanceFormBase } from '../../substance-form/base-classes/substance-f import { ControlledVocabularyService } from '../../controlled-vocabulary/controlled-vocabulary.service'; import { VocabularyTerm } from '../../controlled-vocabulary/vocabulary.model'; */ -import { SubstanceService } from "../../substance/substance.service"; -import { - SubstanceSummary, - SubstanceRelationship, -} from "../../substance/substance.model"; -import { - SpecifiedSubstanceG4mProcess, - SubstanceRelated, -} from "../../substance/substance.model"; -import { SubstanceDetail } from "@gsrs-core/substance/substance.model"; -import { SubstanceFormSsg4mStagesService } from "./substance-form-ssg4m-stages.service"; -import { SubstanceDraftsComponent } from "@gsrs-core/substance-form/substance-drafts/substance-drafts.component"; -import { SpecifiedSubstanceG4mStage } from "@gsrs-core/substance/substance.model"; -import { ConfirmDialogComponent } from "../../../fda/confirm-dialog/confirm-dialog.component"; +import { SubstanceService } from '../../substance/substance.service'; +import { SubstanceSummary, SubstanceRelationship } from '../../substance/substance.model'; +import { SpecifiedSubstanceG4mProcess, SubstanceRelated } from '../../substance/substance.model'; +import { SubstanceDetail } from '@gsrs-core/substance/substance.model'; +import { SubstanceFormSsg4mStagesService } from './substance-form-ssg4m-stages.service'; +import { SpecifiedSubstanceG4mStage } from '@gsrs-core/substance/substance.model'; +import { ConfirmDialogComponent } from '../../../fda/confirm-dialog/confirm-dialog.component'; @Component({ - selector: "app-ssg4m-stages-form", - templateUrl: "./ssg4m-stages-form.component.html", - styleUrls: ["./ssg4m-stages-form.component.scss"], + selector: 'app-ssg4m-stages-form', + templateUrl: './ssg4m-stages-form.component.html', + styleUrls: ['./ssg4m-stages-form.component.scss'] }) export class Ssg4mStagesFormComponent implements OnInit, OnDestroy { public configSettingsDisplay = {}; - private overlayContainer: HTMLElement; configSsg4Form: any; configTitleStage: string; configTitleProcessingMaterials: string; @@ -66,7 +49,7 @@ export class Ssg4mStagesFormComponent implements OnInit, OnDestroy { private scrollToService: ScrollToService, public configService: ConfigService, private dialog: MatDialog - ) {} + ) { } @Input() set stage(stage: SpecifiedSubstanceG4mStage) { @@ -99,7 +82,7 @@ export class Ssg4mStagesFormComponent implements OnInit, OnDestroy { set stageIndex(stageIndex: number) { this.privateStageIndex = stageIndex; // Set the Stage Name - // alert("STAGE INDEX: " + stageIndex); + // alert("STAGE INDEX: " + stageIndex); this.privateStage.stageNumber = String(this.privateStageIndex + 1); } @@ -129,39 +112,30 @@ export class Ssg4mStagesFormComponent implements OnInit, OnDestroy { ngOnInit(): void { // this.substance = this.substanceFormSsg4mStagesService.substance; - const subscription = this.substanceFormService.substance.subscribe( - (substance) => { - this.substance = substance; - } - ); + const subscription = this.substanceFormService.substance.subscribe(substance => { + this.substance = substance; + }); this.subscriptions.push(subscription); - // overlay container for dialogs - this.overlayContainer = this.overlayContainerService.getContainerElement(); - // Get Config variables for SSG4m - this.configSsg4Form = - (this.configService.configData && - this.configService.configData.ssg4Form) || - null; - this.configTitleStage = "Stage"; + this.configSsg4Form = (this.configService.configData && this.configService.configData.ssg4Form) || null; + this.configTitleStage = 'Stage'; this.configTitleProcessingMaterials = "Processing Materials"; if (this.configSsg4Form) { this.configTitleStage = this.configSsg4Form.titles.stage || null; if (!this.configTitleStage) { - this.configTitleStage = "Stage"; + this.configTitleStage = 'Stage'; } - this.configTitleProcessingMaterials = - this.configSsg4Form.titles.processingMaterials || null; + this.configTitleProcessingMaterials = this.configSsg4Form.titles.processingMaterials || null; if (!this.configTitleProcessingMaterials) { - this.configTitleProcessingMaterials = "Processing Materials"; + this.configTitleProcessingMaterials = 'Processing Materials'; } } } ngOnDestroy(): void { // this.substanceFormService.unloadSubstance(); - this.subscriptions.forEach((subscription) => { + this.subscriptions.forEach(subscription => { subscription.unsubscribe(); }); } @@ -169,155 +143,74 @@ export class Ssg4mStagesFormComponent implements OnInit, OnDestroy { getConfigSettings(): void { // Get SSG4 Config Settings from config.json file to show and hide fields in the form let configSsg4Form: any; - configSsg4Form = - (this.configService.configData && - this.configService.configData.ssg4Form) || - null; + configSsg4Form = this.configService.configData && this.configService.configData.ssg4Form || null; // Get 'stage' json values from config const confSettings = configSsg4Form.settingsDisplay.stage; - Object.keys(confSettings).forEach((key) => { + Object.keys(confSettings).forEach(key => { if (confSettings[key] != null) { - if (confSettings[key] === "simple") { + if (confSettings[key] === 'simple') { this.configSettingsDisplay[key] = true; - } else if (confSettings[key] === "advanced") { + } else if (confSettings[key] === 'advanced') { if (this.privateShowAdvancedSettings === true) { this.configSettingsDisplay[key] = true; } else { this.configSettingsDisplay[key] = false; } - } else if (confSettings[key] === "removed") { + } else if (confSettings[key] === 'removed') { this.configSettingsDisplay[key] = false; } } }); } - insertStage( - processIndex: number, - siteIndex: number, - stageIndex: number, - insertDirection?: string - ): void { - this.substanceFormSsg4mStagesService.insertStage( - processIndex, - siteIndex, - stageIndex, - insertDirection - ); + insertStage(processIndex: number, siteIndex: number, stageIndex: number, insertDirection?: string): void { + this.substanceFormSsg4mStagesService.insertStage(processIndex, siteIndex, stageIndex, insertDirection); setTimeout(() => { - this.scrollToService.scrollToElement(`substance-process-0`, "center"); + this.scrollToService.scrollToElement(`substance-process-0`, 'center'); }); } - duplicateStage( - processIndex: number, - siteIndex: number, - stageIndex: number, - insertDirection?: string - ): void { - this.substanceFormSsg4mStagesService.duplicateStage( - processIndex, - siteIndex, - stageIndex, - insertDirection - ); + duplicateStage(processIndex: number, siteIndex: number, stageIndex: number, insertDirection?: string): void { + this.substanceFormSsg4mStagesService.duplicateStage(processIndex, siteIndex, stageIndex, insertDirection); setTimeout(() => { - this.scrollToService.scrollToElement( - `substance-stage-duplicate-0`, - "center" - ); + this.scrollToService.scrollToElement(`substance-stage-duplicate-0`, 'center'); }); } - addCriticalParameter( - processIndex: number, - siteIndex: number, - stageIndex: number - ) { - this.substanceFormSsg4mStagesService.addCriticalParameter( - processIndex, - siteIndex, - stageIndex - ); + addCriticalParameter(processIndex: number, siteIndex: number, stageIndex: number) { + this.substanceFormSsg4mStagesService.addCriticalParameter(processIndex, siteIndex, stageIndex); setTimeout(() => { - this.scrollToService.scrollToElement( - `substance-process-site-stage-criticalParam-0`, - "center" - ); + this.scrollToService.scrollToElement(`substance-process-site-stage-criticalParam-0`, 'center'); }); } - addStartingMaterial( - processIndex: number, - siteIndex: number, - stageIndex: number - ) { - this.substanceFormSsg4mStagesService.addStartingMaterials( - processIndex, - siteIndex, - stageIndex - ); + addStartingMaterial(processIndex: number, siteIndex: number, stageIndex: number) { + this.substanceFormSsg4mStagesService.addStartingMaterials(processIndex, siteIndex, stageIndex); setTimeout(() => { - this.scrollToService.scrollToElement( - `substance-process-site-stage-startMat-0`, - "center" - ); + this.scrollToService.scrollToElement(`substance-process-site-stage-startMat-0`, 'center'); }); } - addProcessingMaterial( - processIndex: number, - siteIndex: number, - stageIndex: number - ) { - this.substanceFormSsg4mStagesService.addProcessingMaterials( - processIndex, - siteIndex, - stageIndex - ); + addProcessingMaterial(processIndex: number, siteIndex: number, stageIndex: number) { + this.substanceFormSsg4mStagesService.addProcessingMaterials(processIndex, siteIndex, stageIndex); setTimeout(() => { - this.scrollToService.scrollToElement( - `substance-process-site-stage-processMat-0`, - "center" - ); + this.scrollToService.scrollToElement(`substance-process-site-stage-processMat-0`, 'center'); }); } - addResultingMaterial( - processIndex: number, - siteIndex: number, - stageIndex: number - ) { - this.substanceFormSsg4mStagesService.addResultingMaterials( - processIndex, - siteIndex, - stageIndex - ); + addResultingMaterial(processIndex: number, siteIndex: number, stageIndex: number) { + this.substanceFormSsg4mStagesService.addResultingMaterials(processIndex, siteIndex, stageIndex); setTimeout(() => { - this.scrollToService.scrollToElement( - `substance-process-site-stage-resultMat-0`, - "center" - ); + this.scrollToService.scrollToElement(`substance-process-site-stage-resultMat-0`, 'center'); }); } confirmDeleteStage() { const dialogRef = this.dialog.open(ConfirmDialogComponent, { - data: { - message: - "Are you sure you want to delele " + - this.configTitleStage + - " " + - (this.stageIndex + 1) + - " for Site " + - (this.siteIndex + 1) + - " for Process " + - (this.processIndex + 1) + - "?", - }, + data: { message: 'Are you sure you want to delele ' + this.configTitleStage + ' ' + (this.stageIndex + 1) + ' for Site ' + (this.siteIndex + 1) + ' for Process ' + (this.processIndex + 1) + '?' } }); - dialogRef.afterClosed().subscribe((result) => { + dialogRef.afterClosed().subscribe(result => { if (result && result === true) { this.deleteStage(); } @@ -325,70 +218,7 @@ export class Ssg4mStagesFormComponent implements OnInit, OnDestroy { } deleteStage(): void { - this.substance.specifiedSubstanceG4m.process[this.processIndex].sites[ - this.siteIndex - ].stages.splice(this.stageIndex, 1); - } - - /** - * Open drafts dialog and add selected draft as a Starting Material for this stage - */ - addStartingMaterialFromDraft( - processIndex: number, - siteIndex: number, - stageIndex: number - ) { - const dialogRef = this.dialog.open(SubstanceDraftsComponent, { - maxHeight: "85%", - width: "70%", - data: { uuid: this.substance ? this.substance.uuid : null }, - }); - if (this.overlayContainer) { - this.overlayContainer.style.zIndex = "1002"; - } - - const sub = dialogRef.afterClosed().subscribe((response) => { - if (this.overlayContainer) { - this.overlayContainer.style.zIndex = null; - } - if (response && response.substance) { - const read = response.substance; - - // Add a new starting material then set its substanceName to reference the selected draft - this.substanceFormSsg4mStagesService.addStartingMaterials( - processIndex, - siteIndex, - stageIndex - ); - - const stageObj = - this.substance.specifiedSubstanceG4m.process[processIndex].sites[ - siteIndex - ].stages[stageIndex]; - const newStartIndex = stageObj.startingMaterials.length - 1; - - // Determine a display name for the substance - let displayName = response.name - ? String(response.name).replace(/<[^>]*>?/gm, "") - : null; - if (!displayName && read.names && read.names.length > 0) { - const n = read.names.find((x) => x.stdName) || read.names[0]; - displayName = n.stdName || n.name || null; - } - - stageObj.startingMaterials[newStartIndex].substanceName = { - refuuid: read.uuid, - name: displayName, - substanceClass: read.substanceClass, - } as any; - - // notify subscribers about the change - this.substanceFormSsg4mStagesService.propertyEmitter.next( - stageObj.startingMaterials - ); - } - sub.unsubscribe(); - }); + this.substance.specifiedSubstanceG4m.process[this.processIndex].sites[this.siteIndex].stages.splice(this.stageIndex, 1); } /* @@ -400,3 +230,4 @@ export class Ssg4mStagesFormComponent implements OnInit, OnDestroy { } */ } + From 2a19a0fcefd95c18f8b8c8c1664d208809aa02be Mon Sep 17 00:00:00 2001 From: Jaroslav Iha Date: Tue, 16 Dec 2025 11:18:32 +0100 Subject: [PATCH 171/408] revert all logic; disable delete button --- .../download-monitor.component.html | 2 +- .../download-monitor.component.ts | 45 ++----------------- 2 files changed, 5 insertions(+), 42 deletions(-) diff --git a/src/app/core/auth/user-downloads/download-monitor/download-monitor.component.html b/src/app/core/auth/user-downloads/download-monitor/download-monitor.component.html index 065e72d03..7f46dce89 100644 --- a/src/app/core/auth/user-downloads/download-monitor/download-monitor.component.html +++ b/src/app/core/auth/user-downloads/download-monitor/download-monitor.component.html @@ -126,7 +126,7 @@
    - diff --git a/src/app/core/auth/user-downloads/download-monitor/download-monitor.component.ts b/src/app/core/auth/user-downloads/download-monitor/download-monitor.component.ts index 29fe6dc8a..2cc8343f6 100644 --- a/src/app/core/auth/user-downloads/download-monitor/download-monitor.component.ts +++ b/src/app/core/auth/user-downloads/download-monitor/download-monitor.component.ts @@ -67,13 +67,9 @@ export class DownloadMonitorComponent implements OnInit, OnDestroy { } cancel() { - const obs = this.authService.changeDownload(this.download.cancelUrl.url).pipe(take(1)); - // keep existing behavior when called from template (subscribe and refresh) - obs.subscribe(response => { + this.authService.changeDownload(this.download.cancelUrl.url).pipe(take(1)).subscribe(response => { this.refresh(); }); - // also return the observable so callers can chain (used by deleteDownload) - return obs; } downloadExport() { @@ -83,42 +79,9 @@ export class DownloadMonitorComponent implements OnInit, OnDestroy { } deleteDownload() { - if (this.download && this.download.removeUrl && this.download.removeUrl.url) { - this.authService.deleteDownload(this.download.removeUrl.url).pipe(take(1)).subscribe(response => { - this.deleted = true; - }); - } else { - // If there is no removeUrl yet, attempt to cancel the download first (which should create the removeUrl), - // then try deleting. If cancel or removeUrl are not available, build the remove url from cancel one, otherwise mark as deleted to remove from view. - if (this.download && this.download.cancelUrl && this.download.cancelUrl.url) { - this.cancel().pipe(take(1)).subscribe(() => { - // After cancel completes, request the latest status to get any newly-created removeUrl - this.authService.getUpdateStatus(this.id).pipe(take(1)).subscribe(response => { - this.download = response; - if (this.download && this.download.removeUrl && this.download.removeUrl.url) { - this.authService.deleteDownload(this.download.removeUrl.url).pipe(take(1)).subscribe(resp => { - this.deleted = true; - }); - } else { - // fallback: if still no removeUrl, try to create it from cancelUrl - if (this.download.cancelUrl?.url) { - this.authService.deleteDownload(this.download.cancelUrl.url.replace('/@cancel', '')).pipe(take(1)).subscribe(resp => { - this.deleted = true; - }); - } else { - this.deleted = true; - } - } - }, err => { - // on error getting status, fallback to hiding the entry - this.deleted = true; - }); - }); - } else { - // No cancel URL either; nothing to call on server — hide it locally - this.deleted = true; - } - } + this.authService.deleteDownload(this.download.removeUrl.url).pipe(take(1)).subscribe(response => { + this.deleted = true; + }); } processQuery(url: string) { From e7d1601bc507d75225e4e23d4537339c66f7ef7d Mon Sep 17 00:00:00 2001 From: Jaroslav Iha Date: Tue, 16 Dec 2025 14:27:49 +0100 Subject: [PATCH 172/408] Add draft test --- .../ssg4m-stages-form.component.html | 6 + .../ssg4m-stages-form.component.ts | 307 ++++++++++++++---- 2 files changed, 256 insertions(+), 57 deletions(-) diff --git a/src/app/core/substance-ssg4m/ssg4m-stages/ssg4m-stages-form.component.html b/src/app/core/substance-ssg4m/ssg4m-stages/ssg4m-stages-form.component.html index b6b891b16..6ba9f861c 100644 --- a/src/app/core/substance-ssg4m/ssg4m-stages/ssg4m-stages-form.component.html +++ b/src/app/core/substance-ssg4m/ssg4m-stages/ssg4m-stages-form.component.html @@ -100,6 +100,12 @@ (click)="addStartingMaterial(processIndex, siteIndex, stageIndex)"> Add Input Material +   +
    diff --git a/src/app/core/substance-ssg4m/ssg4m-stages/ssg4m-stages-form.component.ts b/src/app/core/substance-ssg4m/ssg4m-stages/ssg4m-stages-form.component.ts index 1ac2de87c..0ab0ddebf 100644 --- a/src/app/core/substance-ssg4m/ssg4m-stages/ssg4m-stages-form.component.ts +++ b/src/app/core/substance-ssg4m/ssg4m-stages/ssg4m-stages-form.component.ts @@ -1,12 +1,21 @@ -import { Component, OnInit, OnDestroy, AfterViewInit, Input } from '@angular/core'; -import { MatDialog } from '@angular/material/dialog'; -import { OverlayContainer } from '@angular/cdk/overlay'; -import { ScrollToService } from '../../scroll-to/scroll-to.service'; -import { Subscription } from 'rxjs'; -import { GoogleAnalyticsService } from '../../google-analytics/google-analytics.service'; -import { ConfigService } from '@gsrs-core/config/config.service'; -import { SubstanceCardBaseFilteredList, SubstanceCardBaseList } from '../../substance-form/base-classes/substance-form-base-filtered-list'; -import { SubstanceFormService } from '../../substance-form/substance-form.service'; +import { + Component, + OnInit, + OnDestroy, + AfterViewInit, + Input, +} from "@angular/core"; +import { MatDialog } from "@angular/material/dialog"; +import { OverlayContainer } from "@angular/cdk/overlay"; +import { ScrollToService } from "../../scroll-to/scroll-to.service"; +import { Subscription } from "rxjs"; +import { GoogleAnalyticsService } from "../../google-analytics/google-analytics.service"; +import { ConfigService } from "@gsrs-core/config/config.service"; +import { + SubstanceCardBaseFilteredList, + SubstanceCardBaseList, +} from "../../substance-form/base-classes/substance-form-base-filtered-list"; +import { SubstanceFormService } from "../../substance-form/substance-form.service"; /* import { take } from 'rxjs/operators'; import { ConfigService } from '@gsrs-core/config'; @@ -14,18 +23,25 @@ import { SubstanceFormBase } from '../../substance-form/base-classes/substance-f import { ControlledVocabularyService } from '../../controlled-vocabulary/controlled-vocabulary.service'; import { VocabularyTerm } from '../../controlled-vocabulary/vocabulary.model'; */ -import { SubstanceService } from '../../substance/substance.service'; -import { SubstanceSummary, SubstanceRelationship } from '../../substance/substance.model'; -import { SpecifiedSubstanceG4mProcess, SubstanceRelated } from '../../substance/substance.model'; -import { SubstanceDetail } from '@gsrs-core/substance/substance.model'; -import { SubstanceFormSsg4mStagesService } from './substance-form-ssg4m-stages.service'; -import { SpecifiedSubstanceG4mStage } from '@gsrs-core/substance/substance.model'; -import { ConfirmDialogComponent } from '../../../fda/confirm-dialog/confirm-dialog.component'; +import { SubstanceService } from "../../substance/substance.service"; +import { + SubstanceSummary, + SubstanceRelationship, +} from "../../substance/substance.model"; +import { + SpecifiedSubstanceG4mProcess, + SubstanceRelated, +} from "../../substance/substance.model"; +import { SubstanceDetail } from "@gsrs-core/substance/substance.model"; +import { SubstanceFormSsg4mStagesService } from "./substance-form-ssg4m-stages.service"; +import { SpecifiedSubstanceG4mStage } from "@gsrs-core/substance/substance.model"; +import { ConfirmDialogComponent } from "../../../fda/confirm-dialog/confirm-dialog.component"; +import { SubstanceDraftsComponent } from "@gsrs-core/substance-form/substance-drafts/substance-drafts.component"; @Component({ - selector: 'app-ssg4m-stages-form', - templateUrl: './ssg4m-stages-form.component.html', - styleUrls: ['./ssg4m-stages-form.component.scss'] + selector: "app-ssg4m-stages-form", + templateUrl: "./ssg4m-stages-form.component.html", + styleUrls: ["./ssg4m-stages-form.component.scss"], }) export class Ssg4mStagesFormComponent implements OnInit, OnDestroy { public configSettingsDisplay = {}; @@ -49,7 +65,7 @@ export class Ssg4mStagesFormComponent implements OnInit, OnDestroy { private scrollToService: ScrollToService, public configService: ConfigService, private dialog: MatDialog - ) { } + ) {} @Input() set stage(stage: SpecifiedSubstanceG4mStage) { @@ -82,7 +98,7 @@ export class Ssg4mStagesFormComponent implements OnInit, OnDestroy { set stageIndex(stageIndex: number) { this.privateStageIndex = stageIndex; // Set the Stage Name - // alert("STAGE INDEX: " + stageIndex); + // alert("STAGE INDEX: " + stageIndex); this.privateStage.stageNumber = String(this.privateStageIndex + 1); } @@ -112,30 +128,36 @@ export class Ssg4mStagesFormComponent implements OnInit, OnDestroy { ngOnInit(): void { // this.substance = this.substanceFormSsg4mStagesService.substance; - const subscription = this.substanceFormService.substance.subscribe(substance => { - this.substance = substance; - }); + const subscription = this.substanceFormService.substance.subscribe( + (substance) => { + this.substance = substance; + } + ); this.subscriptions.push(subscription); // Get Config variables for SSG4m - this.configSsg4Form = (this.configService.configData && this.configService.configData.ssg4Form) || null; - this.configTitleStage = 'Stage'; + this.configSsg4Form = + (this.configService.configData && + this.configService.configData.ssg4Form) || + null; + this.configTitleStage = "Stage"; this.configTitleProcessingMaterials = "Processing Materials"; if (this.configSsg4Form) { this.configTitleStage = this.configSsg4Form.titles.stage || null; if (!this.configTitleStage) { - this.configTitleStage = 'Stage'; + this.configTitleStage = "Stage"; } - this.configTitleProcessingMaterials = this.configSsg4Form.titles.processingMaterials || null; + this.configTitleProcessingMaterials = + this.configSsg4Form.titles.processingMaterials || null; if (!this.configTitleProcessingMaterials) { - this.configTitleProcessingMaterials = 'Processing Materials'; + this.configTitleProcessingMaterials = "Processing Materials"; } } } ngOnDestroy(): void { // this.substanceFormService.unloadSubstance(); - this.subscriptions.forEach(subscription => { + this.subscriptions.forEach((subscription) => { subscription.unsubscribe(); }); } @@ -143,74 +165,244 @@ export class Ssg4mStagesFormComponent implements OnInit, OnDestroy { getConfigSettings(): void { // Get SSG4 Config Settings from config.json file to show and hide fields in the form let configSsg4Form: any; - configSsg4Form = this.configService.configData && this.configService.configData.ssg4Form || null; + configSsg4Form = + (this.configService.configData && + this.configService.configData.ssg4Form) || + null; // Get 'stage' json values from config const confSettings = configSsg4Form.settingsDisplay.stage; - Object.keys(confSettings).forEach(key => { + Object.keys(confSettings).forEach((key) => { if (confSettings[key] != null) { - if (confSettings[key] === 'simple') { + if (confSettings[key] === "simple") { this.configSettingsDisplay[key] = true; - } else if (confSettings[key] === 'advanced') { + } else if (confSettings[key] === "advanced") { if (this.privateShowAdvancedSettings === true) { this.configSettingsDisplay[key] = true; } else { this.configSettingsDisplay[key] = false; } - } else if (confSettings[key] === 'removed') { + } else if (confSettings[key] === "removed") { this.configSettingsDisplay[key] = false; } } }); } - insertStage(processIndex: number, siteIndex: number, stageIndex: number, insertDirection?: string): void { - this.substanceFormSsg4mStagesService.insertStage(processIndex, siteIndex, stageIndex, insertDirection); + insertStage( + processIndex: number, + siteIndex: number, + stageIndex: number, + insertDirection?: string + ): void { + this.substanceFormSsg4mStagesService.insertStage( + processIndex, + siteIndex, + stageIndex, + insertDirection + ); setTimeout(() => { - this.scrollToService.scrollToElement(`substance-process-0`, 'center'); + this.scrollToService.scrollToElement(`substance-process-0`, "center"); }); } - duplicateStage(processIndex: number, siteIndex: number, stageIndex: number, insertDirection?: string): void { - this.substanceFormSsg4mStagesService.duplicateStage(processIndex, siteIndex, stageIndex, insertDirection); + duplicateStage( + processIndex: number, + siteIndex: number, + stageIndex: number, + insertDirection?: string + ): void { + this.substanceFormSsg4mStagesService.duplicateStage( + processIndex, + siteIndex, + stageIndex, + insertDirection + ); setTimeout(() => { - this.scrollToService.scrollToElement(`substance-stage-duplicate-0`, 'center'); + this.scrollToService.scrollToElement( + `substance-stage-duplicate-0`, + "center" + ); }); } - addCriticalParameter(processIndex: number, siteIndex: number, stageIndex: number) { - this.substanceFormSsg4mStagesService.addCriticalParameter(processIndex, siteIndex, stageIndex); + addCriticalParameter( + processIndex: number, + siteIndex: number, + stageIndex: number + ) { + this.substanceFormSsg4mStagesService.addCriticalParameter( + processIndex, + siteIndex, + stageIndex + ); setTimeout(() => { - this.scrollToService.scrollToElement(`substance-process-site-stage-criticalParam-0`, 'center'); + this.scrollToService.scrollToElement( + `substance-process-site-stage-criticalParam-0`, + "center" + ); }); } - addStartingMaterial(processIndex: number, siteIndex: number, stageIndex: number) { - this.substanceFormSsg4mStagesService.addStartingMaterials(processIndex, siteIndex, stageIndex); + addStartingMaterial( + processIndex: number, + siteIndex: number, + stageIndex: number + ) { + this.substanceFormSsg4mStagesService.addStartingMaterials( + processIndex, + siteIndex, + stageIndex + ); setTimeout(() => { - this.scrollToService.scrollToElement(`substance-process-site-stage-startMat-0`, 'center'); + this.scrollToService.scrollToElement( + `substance-process-site-stage-startMat-0`, + "center" + ); + }); + } + + addDraft(processIndex: number, siteIndex: number, stageIndex: number) { + const dialogRef = this.dialog.open(SubstanceDraftsComponent, { + maxHeight: "85%", + width: "70%", + data: { uuid: this.substance && this.substance.uuid }, + }); + + const overlayContainer = this.overlayContainerService.getContainerElement(); + if (overlayContainer) { + overlayContainer.style.zIndex = "1002"; + } + + dialogRef.afterClosed().subscribe((response) => { + if (overlayContainer) { + overlayContainer.style.zIndex = null; + } + + if (response === null || response === undefined) { + return; + } + + // dialog may return either an index (number) or the draft object. + let draftObj: any = null; + if (typeof response === "number") { + // try to read from the dialog component instance + const comp = dialogRef.componentInstance as any; + if (comp) { + if (comp.filtered && comp.filtered[response]) { + draftObj = comp.filtered[response]; + } else if (comp.values && comp.values[response]) { + draftObj = comp.values[response]; + } + } + } else if (response && response.substance) { + draftObj = response; + } else { + draftObj = response; + } + + if (!draftObj) { + return; + } + + const substanceObj = draftObj.substance || draftObj; + + // Add a new starting material and populate basic fields from the draft + this.substanceFormSsg4mStagesService.addStartingMaterials( + processIndex, + siteIndex, + stageIndex + ); + + // Locate the newly added starting material (last in the list) + const startList = + this.substance.specifiedSubstanceG4m.process[processIndex].sites[ + siteIndex + ].stages[stageIndex].startingMaterials; + if (!startList || startList.length === 0) { + return; + } + const idx = startList.length - 1; + const newStart = startList[idx]; + + // Determine a primary name for the draft substance + const primaryName = + substanceObj._name || + (substanceObj.names && substanceObj.names.length > 0 + ? substanceObj.names[0].name + : null) || + draftObj.name || + null; + + newStart.substanceName = { + name: primaryName, + refuuid: substanceObj.uuid, + substanceClass: substanceObj.substanceClass, + }; + newStart.verbatimName = primaryName; + + // Scroll to newly added starting material entry + setTimeout(() => { + this.scrollToService.scrollToElement( + `substance-process-site-stage-startMat-0`, + "center" + ); + }); }); } - addProcessingMaterial(processIndex: number, siteIndex: number, stageIndex: number) { - this.substanceFormSsg4mStagesService.addProcessingMaterials(processIndex, siteIndex, stageIndex); + addProcessingMaterial( + processIndex: number, + siteIndex: number, + stageIndex: number + ) { + this.substanceFormSsg4mStagesService.addProcessingMaterials( + processIndex, + siteIndex, + stageIndex + ); setTimeout(() => { - this.scrollToService.scrollToElement(`substance-process-site-stage-processMat-0`, 'center'); + this.scrollToService.scrollToElement( + `substance-process-site-stage-processMat-0`, + "center" + ); }); } - addResultingMaterial(processIndex: number, siteIndex: number, stageIndex: number) { - this.substanceFormSsg4mStagesService.addResultingMaterials(processIndex, siteIndex, stageIndex); + addResultingMaterial( + processIndex: number, + siteIndex: number, + stageIndex: number + ) { + this.substanceFormSsg4mStagesService.addResultingMaterials( + processIndex, + siteIndex, + stageIndex + ); setTimeout(() => { - this.scrollToService.scrollToElement(`substance-process-site-stage-resultMat-0`, 'center'); + this.scrollToService.scrollToElement( + `substance-process-site-stage-resultMat-0`, + "center" + ); }); } confirmDeleteStage() { const dialogRef = this.dialog.open(ConfirmDialogComponent, { - data: { message: 'Are you sure you want to delele ' + this.configTitleStage + ' ' + (this.stageIndex + 1) + ' for Site ' + (this.siteIndex + 1) + ' for Process ' + (this.processIndex + 1) + '?' } + data: { + message: + "Are you sure you want to delele " + + this.configTitleStage + + " " + + (this.stageIndex + 1) + + " for Site " + + (this.siteIndex + 1) + + " for Process " + + (this.processIndex + 1) + + "?", + }, }); - dialogRef.afterClosed().subscribe(result => { + dialogRef.afterClosed().subscribe((result) => { if (result && result === true) { this.deleteStage(); } @@ -218,7 +410,9 @@ export class Ssg4mStagesFormComponent implements OnInit, OnDestroy { } deleteStage(): void { - this.substance.specifiedSubstanceG4m.process[this.processIndex].sites[this.siteIndex].stages.splice(this.stageIndex, 1); + this.substance.specifiedSubstanceG4m.process[this.processIndex].sites[ + this.siteIndex + ].stages.splice(this.stageIndex, 1); } /* @@ -230,4 +424,3 @@ export class Ssg4mStagesFormComponent implements OnInit, OnDestroy { } */ } - From 8345d64ec2af5e4e4996d5d333103a4b082e2de6 Mon Sep 17 00:00:00 2001 From: Jaroslav Iha Date: Wed, 17 Dec 2025 12:43:03 +0100 Subject: [PATCH 173/408] update add draft functionality --- .../ssg4m-stages/ssg4m-stages-form.component.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/app/core/substance-ssg4m/ssg4m-stages/ssg4m-stages-form.component.ts b/src/app/core/substance-ssg4m/ssg4m-stages/ssg4m-stages-form.component.ts index 0ab0ddebf..b14b2e447 100644 --- a/src/app/core/substance-ssg4m/ssg4m-stages/ssg4m-stages-form.component.ts +++ b/src/app/core/substance-ssg4m/ssg4m-stages/ssg4m-stages-form.component.ts @@ -334,9 +334,11 @@ export class Ssg4mStagesFormComponent implements OnInit, OnDestroy { null; newStart.substanceName = { + refPname: primaryName, name: primaryName, refuuid: substanceObj.uuid, - substanceClass: substanceObj.substanceClass, + substanceClass: "reference", + approvalID: substanceObj.approvalID, }; newStart.verbatimName = primaryName; From 1d2dfda5b404876a627691fe90f5b27e64f30af1 Mon Sep 17 00:00:00 2001 From: Jaroslav Iha Date: Wed, 17 Dec 2025 13:33:24 +0100 Subject: [PATCH 174/408] try image rendering --- .../ssg4m-stages-form.component.ts | 30 ++++++++++++++++++- ...g4m-starting-materials-form.component.html | 6 +++- 2 files changed, 34 insertions(+), 2 deletions(-) diff --git a/src/app/core/substance-ssg4m/ssg4m-stages/ssg4m-stages-form.component.ts b/src/app/core/substance-ssg4m/ssg4m-stages/ssg4m-stages-form.component.ts index b14b2e447..003e88c03 100644 --- a/src/app/core/substance-ssg4m/ssg4m-stages/ssg4m-stages-form.component.ts +++ b/src/app/core/substance-ssg4m/ssg4m-stages/ssg4m-stages-form.component.ts @@ -34,6 +34,7 @@ import { } from "../../substance/substance.model"; import { SubstanceDetail } from "@gsrs-core/substance/substance.model"; import { SubstanceFormSsg4mStagesService } from "./substance-form-ssg4m-stages.service"; +import { StructureService } from "@gsrs-core/structure/structure.service"; import { SpecifiedSubstanceG4mStage } from "@gsrs-core/substance/substance.model"; import { ConfirmDialogComponent } from "../../../fda/confirm-dialog/confirm-dialog.component"; import { SubstanceDraftsComponent } from "@gsrs-core/substance-form/substance-drafts/substance-drafts.component"; @@ -64,7 +65,8 @@ export class Ssg4mStagesFormComponent implements OnInit, OnDestroy { private overlayContainerService: OverlayContainer, private scrollToService: ScrollToService, public configService: ConfigService, - private dialog: MatDialog + private dialog: MatDialog, + private structureService: StructureService ) {} @Input() @@ -342,6 +344,32 @@ export class Ssg4mStagesFormComponent implements OnInit, OnDestroy { }; newStart.verbatimName = primaryName; + // If the draft contains a structure (molfile or smiles), interpret it on the server + // to obtain a temporary structure id that can be rendered via the same image API. + try { + const mol = + substanceObj.structure && substanceObj.structure.molfile + ? substanceObj.structure.molfile + : substanceObj.structure && substanceObj.structure.smiles + ? substanceObj.structure.smiles + : null; + if (mol) { + this.structureService.interpretStructure(mol).subscribe( + (response) => { + if (response && response.structure && response.structure.id) { + // store a temp id used by the image directive + (newStart as any).$$tmpStructureId = response.structure.id; + } + }, + (error) => { + // ignore failures to interpret + } + ); + } + } catch (e) { + // swallow any unexpected errors + } + // Scroll to newly added starting material entry setTimeout(() => { this.scrollToService.scrollToElement( diff --git a/src/app/core/substance-ssg4m/ssg4m-starting-materials/ssg4m-starting-materials-form.component.html b/src/app/core/substance-ssg4m/ssg4m-starting-materials/ssg4m-starting-materials-form.component.html index a0c727477..9054de33b 100644 --- a/src/app/core/substance-ssg4m/ssg4m-starting-materials/ssg4m-starting-materials-form.component.html +++ b/src/app/core/substance-ssg4m/ssg4m-starting-materials/ssg4m-starting-materials-form.component.html @@ -19,8 +19,12 @@ *ngIf="configSettingsDisplay['substanceName'] || (configSettingsDisplay['substanceName'] === undefined && true)"> + [subuuid]="startingMaterial.substanceName?.refuuid" [showMorelinks]="true"> + +
    From c712dcf9ec1d1a3c7d90bc677d9e6c582161213c Mon Sep 17 00:00:00 2001 From: Jaroslav Iha Date: Wed, 17 Dec 2025 14:59:57 +0100 Subject: [PATCH 175/408] update image layout --- .../ssg4m-starting-materials-form.component.html | 12 ++++++++---- .../ssg4m-starting-materials-form.component.scss | 11 +++++++++++ 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/src/app/core/substance-ssg4m/ssg4m-starting-materials/ssg4m-starting-materials-form.component.html b/src/app/core/substance-ssg4m/ssg4m-starting-materials/ssg4m-starting-materials-form.component.html index 9054de33b..02954856c 100644 --- a/src/app/core/substance-ssg4m/ssg4m-starting-materials/ssg4m-starting-materials-form.component.html +++ b/src/app/core/substance-ssg4m/ssg4m-starting-materials/ssg4m-starting-materials-form.component.html @@ -17,10 +17,14 @@

    - -
    - - - {{parameter.name}} - - -   - {{parameter.value.type}} + + {{ parameter.name }} + + +   + {{ parameter.value.type }} + + +  -   + {{ parameter.value.average }} +   + {{ parameter.value.units }} + + + +   - - -  - -   - {{parameter.value.average}} -   - {{parameter.value.units}} +   [ + + > - - -   - - - -   - [ - - > - - - < - - - {{parameter.value.low}} - - -  to  - - - {{parameter.value.high}} - - ] - -   - {{parameter.value.units}} -   - (average) - + + < - -   - (average) + + {{ parameter.value.low }} + + +  to  + + + {{ parameter.value.high }} - -  - + ] +   - [ - - > - - - < - - - {{parameter.value.lowLimit}} - - -  to  - - - {{parameter.value.highLimit}} - - ] -  (limits) + {{ parameter.value.units }} +   (average) + + + +   (average) + + +  -   [ + + > + + + < - -  -  - {{parameter.value.nonNumericValue}} + + {{ parameter.value.lowLimit }} + +  to  + + + {{ parameter.value.highLimit }} + + ]  (limits) - + +  -  + {{ parameter.value.nonNumericValue }} + + +
    -
    - + - -
    -
    -
    Amount
    - -
    -
    -
    - -
    +
    +
    +
    +
    Amount
    + +
    +
    +
    +
    - -
    - \ No newline at end of file + +
    diff --git a/src/app/core/substance-form/properties/property-form.component.scss b/src/app/core/substance-form/properties/property-form.component.scss index a2701e7b9..505a29993 100644 --- a/src/app/core/substance-form/properties/property-form.component.scss +++ b/src/app/core/substance-form/properties/property-form.component.scss @@ -1,94 +1,98 @@ .property-form-container { - padding: 30px 10px 12px 10px; - position: relative; - display: flex; + padding: 30px 10px 12px 10px; + position: relative; + display: flex; } -.type, .qualification, .interaction-type { +.type, +.qualification, +.interaction-type { ::ng-deep mat-form-field { width: 100%; } } .notification-backdrop { - position: absolute; - top: 0; - right: 0; - bottom: 0; - left: 0; - display: flex; - z-index: 10; - background-color: var(--notif-backdrop-bg-color); - justify-content: center; - align-items: center; - font-size: 30px; - font-weight: bold; - color: var(--notif-backdrop-color); + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + display: flex; + z-index: 10; + background-color: var(--notif-backdrop-bg-color); + justify-content: center; + align-items: center; + font-size: 30px; + font-weight: bold; + color: var(--notif-backdrop-color); } .referenced-substance { - max-width: 25%; - width: 25%; + max-width: 25%; + width: 25%; } .form-content { - flex-grow: 1; + flex-grow: 1; } .form-row { - display: flex; - justify-content: space-between; - align-items: flex-end; + display: flex; + justify-content: space-between; + align-items: flex-end; - .delete-container { - padding: 0 10px 8px 0; - } + .delete-container { + padding: 0 10px 8px 0; + } - .checkbox-container, .radio-container { - padding-bottom: 16px; - padding-right: 15px; - } + .checkbox-container, + .radio-container { + padding-bottom: 16px; + padding-right: 15px; + } - .name, .type { - flex-grow: 1; - padding-right: 15px; - } + .name, + .type { + flex-grow: 1; + padding-right: 15px; + } } .references-container { - width: 100%; + width: 100%; } .amount-title { - margin-bottom: 10px; - font-weight: bold; + margin-bottom: 10px; + font-weight: bold; } .amount-form-container { - padding: 0 7px; + padding: 0 7px; } .column-checkbox { - ::ng-deep .mat-checkbox-layout { - flex-direction: column-reverse; - align-items: center; - } + ::ng-deep .mat-checkbox-layout { + flex-direction: column-reverse; + align-items: center; + } - ::ng-deep .mat-checkbox-inner-container { - margin-right: unset; - margin-left: unset; - } + ::ng-deep .mat-checkbox-inner-container { + margin-right: unset; + margin-left: unset; + } - ::ng-deep .mat-checkbox-layout .mat-checkbox-label { - padding-left: 0; - font-size: 11px; - padding-bottom: 7.5px; - line-height: 11px - } + ::ng-deep .mat-checkbox-layout .mat-checkbox-label { + padding-left: 0; + font-size: 11px; + padding-bottom: 7.5px; + line-height: 11px; + } } .parameters-title { - margin-bottom: 0; + display: flex; + align-items: center; + margin-bottom: 0; } - - diff --git a/src/app/core/substance-form/references/reference-form.component.html b/src/app/core/substance-form/references/reference-form.component.html index 6cb4fe58e..5900b6fdd 100644 --- a/src/app/core/substance-form/references/reference-form.component.html +++ b/src/app/core/substance-form/references/reference-form.component.html @@ -64,7 +64,8 @@
    - @@ -113,7 +114,8 @@
    - +
    diff --git a/src/app/core/substance-form/substance-form-change-reason/substance-form-change-reason.component.html b/src/app/core/substance-form/substance-form-change-reason/substance-form-change-reason.component.html index 763d29a22..ebd9cf5d1 100644 --- a/src/app/core/substance-form/substance-form-change-reason/substance-form-change-reason.component.html +++ b/src/app/core/substance-form/substance-form-change-reason/substance-form-change-reason.component.html @@ -1,9 +1,11 @@ - - {{ hintText() }} + {{ placeholderText() }} + + {{ hintText() }} diff --git a/src/app/core/substance-form/tag-selector/tag-selector.component.scss b/src/app/core/substance-form/tag-selector/tag-selector.component.scss index 9b1b755f3..5a581fe0e 100644 --- a/src/app/core/substance-form/tag-selector/tag-selector.component.scss +++ b/src/app/core/substance-form/tag-selector/tag-selector.component.scss @@ -7,7 +7,7 @@ } ::ng-deep mat-chip-grid { - margin-top: 5px; + // margin-top: 5px; } .mat-mdc-standard-chip:not(.mdc-evolution-chip--disabled) { diff --git a/src/app/core/substance-ssg2/ssg2-manufacturing/ssg2-manufacturing.component.html b/src/app/core/substance-ssg2/ssg2-manufacturing/ssg2-manufacturing.component.html index fbde86f28..63f0e31c0 100644 --- a/src/app/core/substance-ssg2/ssg2-manufacturing/ssg2-manufacturing.component.html +++ b/src/app/core/substance-ssg2/ssg2-manufacturing/ssg2-manufacturing.component.html @@ -1,92 +1,137 @@
    - -
    - -

    -
    +
    +

    -
    -
    - + - + - + - + -
    - - + Product Method Description + +
    + -
    - -
    -
    +
    +
    Organization:    
    - + Search Organization Name + -
    -
    +
    OR Enter a new Organization
    - - + Organization ID + - + Organization Name + - + - + - -
    - + + -
    -
    +
    +
    Sites:     @@ -31,202 +34,381 @@ --> - + - - + + Average + - - + + Low Limit + - - + + High Limit +
    - + Units - Clear selection + Clear selection - {{unit.display}} + {{ unit.display }} {{privateSubstanceAmount.units}} (not in CV) - Other (New Value) + *ngIf=" + privateSubstanceAmount.units && + !inCV(amountUnitList, privateSubstanceAmount.units) && + privateSubstanceAmount.units !== '' + " + value="{{ privateSubstanceAmount.units }}" + >{{ privateSubstanceAmount.units }} (not in CV) + Other (New Value) - + Custom Value +
    - + +
    - + -
    - +
    +
    - + Amount Type - Clear selection + Clear selection - {{type.display}} + {{ type.display }} {{privateSubstanceAmount.type}} (not in CV) - Other (New Value) + *ngIf=" + privateSubstanceAmount.type && + !inCV(amountTypeList, privateSubstanceAmount.type) && + privateSubstanceAmount.type !== '' + " + value="{{ privateSubstanceAmount.type }}" + >{{ privateSubstanceAmount.type }} (not in CV) + Other (New Value)
    - + Custom Value +
    - - + + Low + - - + + High + - - + + Non-numric Value +
    -
    +

    Parameters -

    -
    - - {{parameter.name}} + {{ parameter.name }}   - {{parameter.value.type}} + {{ parameter.value.type }} - -  - -   - {{parameter.value.average}} + +  -   + {{ parameter.value.average }}   - {{parameter.value.units}} + {{ parameter.value.units }} - - -   - - + + +   - -   - [ - +   [ + > - - < - - {{parameter.value.low}} - - -  to  - - - {{parameter.value.high}} - - ] - -   - {{parameter.value.units}} -   - (average) - + + < - -   - (average) + + {{ parameter.value.low }} - -  - + +  to  + + + {{ parameter.value.high }} + + ] +   - [ - - > - - - < - - {{parameter.value.lowLimit}} - - -  to  - - - {{parameter.value.highLimit}} - - ] -  (limits) - - -  -  - {{parameter.value.nonNumericValue}} - + {{ parameter.value.units }} +   (average) + + + +   (average) + + +  -   [ + + > + + + < + + + {{ parameter.value.lowLimit }} + + +  to  - + + {{ parameter.value.highLimit }} + + ]  (limits) + + +  -  + {{ parameter.value.nonNumericValue }} + + +
    -
    @@ -285,4 +467,4 @@

    --> -

    \ No newline at end of file +
    diff --git a/src/app/core/substance-ssg4m/ssg4m-process/ssg4m-process-form.component.html b/src/app/core/substance-ssg4m/ssg4m-process/ssg4m-process-form.component.html index c0ed05480..92baa8509 100644 --- a/src/app/core/substance-ssg4m/ssg4m-process/ssg4m-process-form.component.html +++ b/src/app/core/substance-ssg4m/ssg4m-process/ssg4m-process-form.component.html @@ -8,78 +8,163 @@
    - - -
    - - - + + Process Name + - + - + -
    + +
    - - + + Process Description + - - + + Comments + -
    +
    +
    -
    -
    - Site  {{siteIndex + 1}} of - {{process.sites.length}} +
    +
    + + Site  {{ siteIndex + 1 }} of {{ process.sites.length }} - +
    -
    @@ -87,8 +172,20 @@
    -
    Click on Show All Parameters checkbox to add a Site
    -
    +
    + Click on Show All Parameters checkbox to add a Site +
    +
    -
    \ No newline at end of file +
    diff --git a/src/app/core/substance-ssg4m/ssg4m-processing-materials/ssg4m-processing-materials-form.component.html b/src/app/core/substance-ssg4m/ssg4m-processing-materials/ssg4m-processing-materials-form.component.html index 2723ccc7b..433cf18a5 100644 --- a/src/app/core/substance-ssg4m/ssg4m-processing-materials/ssg4m-processing-materials-form.component.html +++ b/src/app/core/substance-ssg4m/ssg4m-processing-materials/ssg4m-processing-materials-form.component.html @@ -1,5 +1,5 @@
    -
    +
    @@ -12,207 +12,384 @@
    -
    -
    +
    - +
    +
    - - + + Material DIsplay Name + - -
    + +
    +
    - + - - + + Comments +
    -
    +
    Amount -
    - {{displayAmount(processingMaterial.amount)}} + {{ displayAmount(processingMaterial.amount) }}
    -
    +
    +
    - -
    +
    -
    - - Download - - + Download + +
    - +
    Uploading
    -
    +
    Error: There was a problem uploading this document
    -
    -
    + *ngIf=" + configSettingsDisplay['acceptanceCriteria'] || + (configSettingsDisplay['acceptanceCriteria'] === undefined && true) + " + > +
    Acceptance Criterias:   -
    -
    +
    - - + Acceptance Criteria + - + (valueChange)="updateAcceptanceCriteriaType($event)" + >
    -
    -
    +
    +
    -
    - +
    +
    -
    -
    + *ngIf=" + configSettingsDisplay['manufacturer'] || + (configSettingsDisplay['manufacturer'] === undefined && true) + " + > +
    +
    Manufacturer Details:   -
    -
    - +
    - + Manufacturer Name + - + (valueChange)=" + updateManufacturerIdType(manufacturerIndex, $event) + " + > - + Manufacturer ID + - + Lot No + - +
    -
    -
    -
    +
    +
    -
    - -
    -
    +
    + +
    + + + -
    +
    - +
    - -
    \ No newline at end of file + diff --git a/src/app/core/substance-ssg4m/ssg4m-resulting-materials/ssg4m-resulting-materials-form.component.html b/src/app/core/substance-ssg4m/ssg4m-resulting-materials/ssg4m-resulting-materials-form.component.html index 54a44a98a..8482695f4 100644 --- a/src/app/core/substance-ssg4m/ssg4m-resulting-materials/ssg4m-resulting-materials-form.component.html +++ b/src/app/core/substance-ssg4m/ssg4m-resulting-materials/ssg4m-resulting-materials-form.component.html @@ -1,5 +1,5 @@
    -
    +
    @@ -12,118 +12,215 @@
    - -
    -
    +
    - +
    +
    - - + + Material Display Name + - -
    + +
    +
    - + - - + + Comments +
    -
    +
    Amount -
    - {{displayAmount(resultingMaterial.amount)}} + {{ displayAmount(resultingMaterial.amount) }}
    -
    +
    +
    -
    + *ngIf=" + configSettingsDisplay['acceptanceCriteria'] || + (configSettingsDisplay['acceptanceCriteria'] === undefined && true) + " + > +
    Acceptance Criterias:   -
    -
    +
    - + Acceptance Criteria + - + (valueChange)=" + updateAcceptanceCriteriaType($event, acceptanceIndex) + " + >
    -
    -
    +
    +
    -
    - - -
    -
    - -
    \ No newline at end of file + + + + + + + diff --git a/src/app/core/substance-ssg4m/ssg4m-starting-materials/ssg4m-starting-materials-form.component.html b/src/app/core/substance-ssg4m/ssg4m-starting-materials/ssg4m-starting-materials-form.component.html index a0c727477..bd7abd78a 100644 --- a/src/app/core/substance-ssg4m/ssg4m-starting-materials/ssg4m-starting-materials-form.component.html +++ b/src/app/core/substance-ssg4m/ssg4m-starting-materials/ssg4m-starting-materials-form.component.html @@ -1,73 +1,131 @@
    -
    +
    -
    -
    -
    +
    - +
    +
    - - + + Material Display Name + - +
    - + - - + + Comments +
    -
    +
    Amount -
    - {{displayAmount(startingMaterial.amount)}} + {{ displayAmount(startingMaterial.amount) }}
    @@ -75,143 +133,266 @@
    - -
    +
    -
    -
    - - Download - - + Download + +
    - +
    Uploading
    -
    +
    Error: There was a problem uploading this document
    -
    - +
    +
    -
    + *ngIf=" + configSettingsDisplay['acceptanceCriteria'] || + (configSettingsDisplay['acceptanceCriteria'] === undefined && true) + " + > +
    Acceptance Criterias:   -
    -
    - +
    - + Acceptance Criteria + - + (valueChange)=" + updateAcceptanceCriteriaType($event, acceptanceIndex) + " + >
    -
    - -
    +
    +
    -
    - +
    +
    -
    -
    + *ngIf=" + configSettingsDisplay['manufacturer'] || + (configSettingsDisplay['manufacturer'] === undefined && true) + " + > +
    +
    Manufacturer Details:   -
    -
    +
    - + Manufacturer Name + - + (valueChange)=" + updateManufacturerIdType(manufacturerIndex, $event) + " + > - + Manufacturer ID + - + Lot No +
    -
    -
    +
    +
    -
    - -
    -
    +
    + +
    + +
    + -
    -
    +
    +
    - +
    - @@ -274,5 +455,4 @@
    --> - -
    \ No newline at end of file + diff --git a/src/app/core/substance-ssg4m/ssg4m-starting-materials/ssg4m-starting-materials-form.component.scss b/src/app/core/substance-ssg4m/ssg4m-starting-materials/ssg4m-starting-materials-form.component.scss index 4f1d02ba4..4b0b78ac2 100644 --- a/src/app/core/substance-ssg4m/ssg4m-starting-materials/ssg4m-starting-materials-form.component.scss +++ b/src/app/core/substance-ssg4m/ssg4m-starting-materials/ssg4m-starting-materials-form.component.scss @@ -26,12 +26,12 @@ align-items: flex-end; .delete-container { - padding: 0 10px 8px 0; + padding: 0 10px 8px 0; } .col { - flex-grow: 1; - padding-right: 25px; + flex-grow: 1; + padding-right: 25px; } .col-1-1 { @@ -54,10 +54,10 @@ text-align: left !important; position: relative; img { - width: 100%; - height: auto; - display: block; - max-width: 220px; + width: 100%; + height: auto; + display: block; + max-width: 220px; } } @@ -170,9 +170,19 @@ hr.style { } .amount-display { - padding-top:11px; + padding-top: 11px; } .middle-fill { flex: 1 1 auto; } + +::ng-deep .search-form-field .mat-mdc-form-field-infix { + padding-top: 24px !important; + padding-bottom: 8px !important; +} + +// ::ng-deep .mat-mdc-form-field { +// line-height: 1.125 !important; +// font-size: 14px !important; +// } diff --git a/src/app/fda/impurities/impurities-form/impurities-details-form/impurities-details-form.component.html b/src/app/fda/impurities/impurities-form/impurities-details-form/impurities-details-form.component.html index 5f19bd8b9..a7a255d4d 100644 --- a/src/app/fda/impurities/impurities-form/impurities-details-form/impurities-details-form.component.html +++ b/src/app/fda/impurities/impurities-form/impurities-details-form/impurities-details-form.component.html @@ -1,11 +1,17 @@ -
    +
    -
    Impurities {{(impuritiesDetailsIndex+1)}}
    +
    + Impurities {{ impuritiesDetailsIndex + 1 }} +
    -
    @@ -14,121 +20,240 @@
    - -
    - +
    + -
    - {{impuritiesDetails.relatedSubstanceUnii}} +
    + {{ impuritiesDetails.relatedSubstanceUnii }}
    -
    - - + + Source Impurity Name + - + - - + + Comments +
    - + - - + + - +
    -
    - -
    -
    -
    - +
    - + (valueChange)="identityCriteria.identityCriteriaType = $event" + > - - + + - + -
    -
    -
    -
    - +
    +
    -
    -
    - -
    - - \ No newline at end of file + diff --git a/src/app/fda/impurities/impurities-form/impurities-form.component.html b/src/app/fda/impurities/impurities-form/impurities-form.component.html index afdc57a55..535b754cb 100644 --- a/src/app/fda/impurities/impurities-form/impurities-form.component.html +++ b/src/app/fda/impurities/impurities-form/impurities-form.component.html @@ -1,79 +1,143 @@
    - -     - - - Export JSON -     +     + + + Export JSON     - + - - -     - - - - View Impurities -     +     + + + + View Impurities     -     +     -   - +       - - +
    + [ngClass]="{ + 'submission-messages': true, + collapsed: !showSubmissionMessages, + expanded: showSubmissionMessages, + }" + >
    - {{submissionMessage}} + {{ submissionMessage }}
    - -
    + +
    Please correct or dismiss the following errors and submit again:
    -
    -
    - {{message.messageType}}
    -
    {{message.message}}
    {{link.text}}
    -
    @@ -81,14 +145,22 @@
    - +
    -
    @@ -100,137 +172,269 @@
    - {{title}} + {{ title }}
    -
    +
    - Created By: {{impurities.createdBy}}    + Created By: + {{ impurities.createdBy }}    Create Date: - {{impurities.creationDate|date: 'MM/dd/yyyy hh:mm:ss a'}}    - Modified By: {{impurities.modifiedBy}}    + {{ + impurities.creationDate | date: "MM/dd/yyyy hh:mm:ss a" + }}    Modified By: + {{ impurities.modifiedBy }}    Modify Date: - {{impurities.lastModifiedDate|date: 'MM/dd/yyyy hh:mm:ss a'}} + {{ + impurities.lastModifiedDate | date: "MM/dd/yyyy hh:mm:ss a" + }}
    - + Show Advanced Fields
    - -
    +
    +
    - + - + - - + + Source ID + - + - - + Product ID + - +
    - - + Submitter Name + - - + + Product/Substance Name + - + - + Date Type Date - - - + + + -
    - -
    - +
    +
    - Substance  -
    -
    + Substance 
    +
    -
    -
    - +
    +
    - -
    - +
    +
    @@ -240,20 +444,20 @@ - + - -
    -
    +
    +
    -
    -


    \ No newline at end of file +


    diff --git a/src/app/fda/impurities/impurities-form/impurities-inorganic-test-form/impurities-inorganic-form-test.component.html b/src/app/fda/impurities/impurities-form/impurities-inorganic-test-form/impurities-inorganic-form-test.component.html index a65db1dd8..3796d0765 100644 --- a/src/app/fda/impurities/impurities-form/impurities-inorganic-test-form/impurities-inorganic-form-test.component.html +++ b/src/app/fda/impurities/impurities-form/impurities-inorganic-test-form/impurities-inorganic-form-test.component.html @@ -1,11 +1,18 @@ -
    +
    -
    Inorganic Impurities Test  {{(inorganicTestIndex+1)}}
    +
    + Inorganic Impurities Test  {{ inorganicTestIndex + 1 }} +
    -
    @@ -13,76 +20,156 @@ -
    - + - + - - + + Source ID +
    - - + + Test + - +
    - - + + Test Description + - - + + Comments + -
    - + Inorganic Impurities            -
    -
    - - -
    +
    + + +
    - -

    +

    - -
    -
    \ No newline at end of file +
    diff --git a/src/app/fda/impurities/impurities-form/impurities-residual-solvents-test-form/impurities-residual-solvents-test.component.html b/src/app/fda/impurities/impurities-form/impurities-residual-solvents-test-form/impurities-residual-solvents-test.component.html index 2b1464a88..0cb6bc03d 100644 --- a/src/app/fda/impurities/impurities-form/impurities-residual-solvents-test-form/impurities-residual-solvents-test.component.html +++ b/src/app/fda/impurities/impurities-form/impurities-residual-solvents-test-form/impurities-residual-solvents-test.component.html @@ -1,11 +1,20 @@ -
    +
    -
    Residual Solvents Test  {{(residualSolventsTestIndex+1)}}
    +
    + Residual Solvents Test  {{ residualSolventsTestIndex + 1 }} +
    -
    @@ -13,73 +22,154 @@ -
    - + - + - - + + Source ID +
    - - + + Test + - +
    - - + + Test Description + - - + + Comments +
    - + Residual Solvents            -
    -
    - +
    +
    @@ -94,8 +184,7 @@ --> - -

    +

    - - -
    \ No newline at end of file +
    diff --git a/src/app/fda/impurities/impurities-form/impurities-substance-form/impurities-substance-form.component.html b/src/app/fda/impurities/impurities-form/impurities-substance-form/impurities-substance-form.component.html index e927f84b6..6d3d645e8 100644 --- a/src/app/fda/impurities/impurities-form/impurities-substance-form/impurities-substance-form.component.html +++ b/src/app/fda/impurities/impurities-form/impurities-substance-form/impurities-substance-form.component.html @@ -1,10 +1,16 @@
    -
    Substance  {{(impuritiesSubstanceIndex+1)}}
    +
    + Substance  {{ impuritiesSubstanceIndex + 1 }} +
    -
    @@ -13,50 +19,103 @@
    - -
    - +
    + -
    - {{impuritiesSubstance.approvalID}} +
    + {{ impuritiesSubstance.approvalID }}
    - - + + - - + + - +
    - - + +
    - -
    -
    -
    +
    + +
    + +
    @@ -64,42 +123,65 @@
    - Test  -
    -
    + Test 
    +
    -
    -
    - {{errorMessage}} + {{ errorMessage }}
    -
    - +
    +
    - - -
    +
    - Residual Solvents Test  -
    -
    + Residual Solvents Test 
    +
    -
    -
    - +
    +
    @@ -188,7 +289,6 @@ - - -
    +
    - Inorganic Impurities Test  -
    -
    + Inorganic Impurities Test 
    +
    -
    -
    - +
    +
    @@ -273,11 +392,8 @@
    --> - -
    - -

    \ No newline at end of file +

    diff --git a/src/app/fda/impurities/impurities-form/impurities-substance-form/impurities-substance-form.component.scss b/src/app/fda/impurities/impurities-form/impurities-substance-form/impurities-substance-form.component.scss index fe090c8fb..de9d8e54a 100644 --- a/src/app/fda/impurities/impurities-form/impurities-substance-form/impurities-substance-form.component.scss +++ b/src/app/fda/impurities/impurities-form/impurities-substance-form/impurities-substance-form.component.scss @@ -1,279 +1,280 @@ .top-fixed { - position: fixed; - display: flex; - flex-direction: column; - top: 64px; - width: 100%; - background-color: var(--regular-white-color); - align-items: center; - justify-content: center; - box-shadow: 0px 3px 3px -2px var(--box-shadow-color), 0px 3px 4px 0px var(--box-shadow-color-2), 0px 1px 8px 0px var(--box-shadow-color-3); - z-index: 1001; - } + position: fixed; + display: flex; + flex-direction: column; + top: 64px; + width: 100%; + background-color: var(--regular-white-color); + align-items: center; + justify-content: center; + box-shadow: + 0px 3px 3px -2px var(--box-shadow-color), + 0px 3px 4px 0px var(--box-shadow-color-2), + 0px 1px 8px 0px var(--box-shadow-color-3); + z-index: 1001; +} - .height30px { - height: 30px; - } - - .form-content-container { - overflow: hidden; - padding-top: 110px; - } - - .scrollable-container { - padding-top: 15px; - } - - .cards-container { - width: 100%; - } - - .title_box { - width: 1140px; - display: flex; - justify-content: space-between; - margin-bottom: 10px; - } - - .title { - font-size: 24px; - font-weight: 600px; - font-family: Arial, Helvetica, sans-serif; - /*padding-left: 70px; */ - } - - .titleblue { - font-size: 18px; - font-weight: 700px; - font-family: Arial, Helvetica, sans-serif; - color: var(--regular-blue-color); - padding-top: 10px; - } - - .divflex { - display: flex; - } - - .flex-container { - display: flex; - } +.height30px { + height: 30px; +} - .flex-container { - display: flex; - } - - .flex-item { - display: flex; - flex-direction: column; - } - - .mat-card { - max-width: 1140px; - } - - .row { - width: 100%; - } - - .form-row { - display: flex; - width: 100%; - } - - .col-6-1 { - width: calc((100% - 25px * 5) / 6); - margin-right: 25px; - } - - .col-6-1:last-child { - margin-right: 0px; - } - - .col-6-2 { - width: calc((100% - 25px * 2) / 3); - margin-right: 25px; - } - - .col-6-2:last-child { - margin-right: 0px; - } - - .col-3-1 { - width: calc((100% - 25px * 2) / 3); - margin-right: 25px; - } - - .col-3-1:last-child { - margin-right: 0px; - } - - .col-6-4 { - width: calc((100% - 20px) / (6/4)); - margin-right: 20px; - } - - .col-6-4:last-child { - margin-right: 0px; - } - - .col-6-5 { - width: calc((100% - 10px) / (6/5)); - margin-right: 10px; - } - - .col-6-5:last-child { - margin-right: 0px; - } - - .col-5-more { - width: calc((100% + 100px) / (6/5)); - margin-right: 10px; - } - - .col-6-6 { - width: 100%; - } - - .col-4-1 { - width: calc((100% - 30px * 3) / 4); - margin-right: 30px; - } - - .col-4-1:last-child { - margin-right: 0px; - } - - .hide-show-messages { - margin-left: 10px; - border: 1px solid; +.form-content-container { + overflow: hidden; + padding-top: 110px; +} + +.scrollable-container { + padding-top: 15px; +} + +.cards-container { + width: 100%; +} + +.title_box { + width: 1140px; + display: flex; + justify-content: space-between; + margin-bottom: 10px; +} + +.title { + font-size: 24px; + font-weight: 600px; + font-family: Arial, Helvetica, sans-serif; + /*padding-left: 70px; */ +} + +.titleblue { + font-size: 18px; + font-weight: 700px; + font-family: Arial, Helvetica, sans-serif; + color: var(--regular-blue-color); + padding-top: 10px; +} + +.divflex { + display: flex; +} + +.flex-container { + display: flex; +} + +.flex-container { + display: flex; +} + +.flex-item { + display: flex; + flex-direction: column; +} + +.mat-card { + max-width: 1140px; +} + +.row { + width: 100%; +} + +.form-row { + display: flex; + width: 100%; +} + +.col-6-1 { + width: calc((100% - 25px * 5) / 6); + margin-right: 25px; +} + +.col-6-1:last-child { + margin-right: 0px; +} + +.col-6-2 { + width: calc((100% - 25px * 2) / 3); + margin-right: 25px; +} + +.col-6-2:last-child { + margin-right: 0px; +} + +.col-3-1 { + width: calc((100% - 25px * 2) / 3); + margin-right: 25px; +} + +.col-3-1:last-child { + margin-right: 0px; +} + +.col-6-4 { + width: calc((100% - 20px) / (6 / 4)); + margin-right: 20px; +} + +.col-6-4:last-child { + margin-right: 0px; +} + +.col-6-5 { + width: calc((100% - 10px) / (6 / 5)); + margin-right: 10px; +} + +.col-6-5:last-child { + margin-right: 0px; +} + +.col-5-more { + width: calc((100% + 100px) / (6 / 5)); + margin-right: 10px; +} + +.col-6-6 { + width: 100%; +} + +.col-4-1 { + width: calc((100% - 30px * 3) / 4); + margin-right: 30px; +} + +.col-4-1:last-child { + margin-right: 0px; +} + +.hide-show-messages { + margin-left: 10px; + border: 1px solid; +} + +.actions-container { + max-width: 1028px; + width: 100%; + background-color: var(--regular-white-color); + padding: 10px; + display: flex; +} + +.dismiss-container { + display: flex; +} + +.middle-fill { + flex: 1 1 auto; +} + +.submission-messages { + overflow: hidden; + height: auto; + -webkit-transition: all 500ms ease-out; + transition: all 500ms ease-out; + max-width: 1028px; + width: 100%; + background-color: var(--regular-white-color); + display: flex; + flex-direction: column; + + &.collapsed { + max-height: 0; } - - .actions-container { - max-width: 1028px; - width: 100%; - background-color: var(--regular-white-color); + + &.expanded { + max-height: 500px; + overflow-y: auto; padding: 10px; - display: flex; } - - .dismiss-container { - display: flex; - } - - .middle-fill { - flex: 1 1 auto; + + .submission-message { + font-weight: 500; + text-align: center; } - - .submission-messages { - overflow: hidden; - height: auto; - -webkit-transition: all 500ms ease-out; - transition: all 500ms ease-out; - max-width: 1028px; - width: 100%; - background-color: var(--regular-white-color); + + .validation-message { display: flex; - flex-direction: column; - - &.collapsed { - max-height: 0; - } - - &.expanded { - max-height: 500px; - overflow-y: auto; - padding: 10px; - } - - .submission-message { + padding: 5px 0; + align-items: center; + + .message-type { + text-transform: uppercase; font-weight: 500; - text-align: center; - } - - .validation-message { - display: flex; - padding: 5px 0; - align-items: center; - - .message-type { - text-transform: uppercase; - font-weight: 500; - margin-right: 20px; - padding:10px; - border-radius:3px; - } - } - - .dismiss-container { - display: flex; - } - - .warning-message { - color: var(--warning-dialog-color); - background-color: var(--warning-dialog-bg-color); - - } - - .error-message { - color: var(--error-dialog-color); - background-color: var(--error-dialog-bg-color); + margin-right: 20px; + padding: 10px; + border-radius: 3px; } - - } - - .divflexrow { - display: flex; - flex-direction: row; - align-items: flex-start; - justify-content: flex-start; } - - .details-container { - width: 100%; + + .dismiss-container { display: flex; - align-items: center; - justify-content: center; - } - - .details-box { - max-width: 1028px; - width: 100%; - box-sizing: border-box; - margin-bottom: 20px; } - - .margintopneg10px { - margin-top: -10px; + + .warning-message { + color: var(--warning-dialog-color); + background-color: var(--warning-dialog-bg-color); } - - .margintop5px { - margin-top: 5px; + + .error-message { + color: var(--error-dialog-color); + background-color: var(--error-dialog-bg-color); } +} + +.divflexrow { + display: flex; + flex-direction: row; + align-items: flex-start; + justify-content: flex-start; +} + +.details-container { + width: 100%; + display: flex; + align-items: center; + justify-content: center; +} + +.details-box { + max-width: 1028px; + width: 100%; + box-sizing: border-box; + margin-bottom: 20px; +} + +.margintopneg10px { + margin-top: -10px; +} + +.margintop5px { + margin-top: 5px; +} .margintop10px { margin-top: 10px; } - + .margintop15px { margin-top: 15px; } - + .margintop20px { margin-top: 20px; } - + .margintop90px { margin-top: 90px; } - + .margintop12px { margin-top: 12px; } - + .marginleft20px { margin-left: 20px; } - + .marginleft25px { margin-left: 25px; } @@ -281,7 +282,7 @@ .marginleft30px { margin-left: 30px; } - + .marginright30px { margin-right: 30px; } @@ -289,11 +290,11 @@ .padtop5px { padding-top: 5px; } - + .padtop10px { padding-top: 10px; } - + .padtop17px { padding-top: 17px; } @@ -301,11 +302,11 @@ .padleft185px { padding-left: 185px; } - + .borderlightgray { border: 1px solid rgb(224, 224, 224); } - + .borderyellow { border: 1px solid var(--yellow-color); } @@ -313,11 +314,11 @@ .bordergray { border: 1px solid var(--regular-grey-color); } - + .bordergreen { border: 1px solid var(--regular-green-color); } - + .borderstructure { border: 1px solid rgb(231, 231, 231); box-shadow: 2px 2px #eeeeee; @@ -327,159 +328,161 @@ .width40px { width: 40px; } - + .width50px { width: 50px; } - + .width200px { width: 200px; } - + .width25percent { width: 25%; } - - .width32percent { - width: 32%; - } - - .width75percent { - width: 75%; - } - - .colorgray { - color: var(--regular-grey-color); - } - - .colorred { - color: var(--regular-red-color); - } - - .font11px { - font-size: 11px; - } - - .font12px { - font-size: 12px; + +.width32percent { + width: 32%; +} + +.width75percent { + width: 75%; +} + +.colorgray { + color: var(--regular-grey-color); +} + +.colorred { + color: var(--regular-red-color); +} + +.font11px { + font-size: 11px; +} + +.font12px { + font-size: 12px; +} + +.font13px { + font-size: 13px; +} + +.textalignleft { + text-align: left; +} + +.textalignright { + text-align: right; +} + +.textaligncenter { + text-align: center; +} + +.displayinlineblock { + display: inline-block; +} + +.floatleft { + float: left; +} + +.disabled { + cursor: not-allowed; +} + +.disabledfield { + cursor: not-allowed; + color: var(--regular-red-color); +} + +/* CV INPUT OVERWRITE WIDTH */ +.cvwidth { + .mat-form-field { + width: 20%; } - - .font13px { - font-size: 13px; +} + +.cvwidth2 { + width: 300px; +} + +hr { + border: none; + border-top: 3px solid var(--regular-green-color); + color: var(--hr-color); + overflow: visible; + text-align: center; + height: 5px; +} + +.tabStyle { + width: 200px; + height: 30px; + border: 1px solid var(--regular-grey-color); + background: var(--tabstyle-bg-color-2); +} + +.mat-form-field-style > { + /* OVERWRITE MATERIAL INPUT FIELDS */ + .mat-form-field-infix { + color: var(--regular-blue-color); } - - .textalignleft { - text-align: left; + + .mat-select-value { + color: var(--regular-blue-color); } - .textalignright { - text-align: right; + .mat-form-field-label { + color: var(--mat-form-field-label-color) !important; } - - .textaligncenter { - text-align: center; + + .mat-form-field-underline { + background-color: var(--mat-form-field-underline-bg-color) !important; } - - .displayinlineblock { - display: inline-block; + + /*Focused: change color of label*/ + .mat-focused .mat-form-field-label { + color: var(--mat-form-field-focused-color) !important; } - - .floatleft { - float: left; + + /*Focused: change color of underline*/ + .mat-form-field-ripple { + background-color: var(--mat-form-field-focused-color) !important; } - - .disabled { - cursor: not-allowed; - } - - .disabledfield { + + .mat-form-field-disabled .mat-form-field-underline { + background-image: linear-gradient( + to right, + var(--img-linear-gradient-start-color) 0, + var(--textarea-dark-border-color) 10%, + var(--img-linear-gradient-color) 0 + ) !important; + background-size: 1px 100% !important; + background-repeat: repeat-x !important; cursor: not-allowed; - color: var(--regular-red-color); - } - - /* CV INPUT OVERWRITE WIDTH */ - .cvwidth { - .mat-form-field { - width: 20%; - } } - - .cvwidth2 { - width: 300px; - } - - hr { - border: none; - border-top: 3px solid var(--regular-green-color); - color: var(--hr-color); - overflow: visible; - text-align: center; - height: 5px; - } - - .tabStyle { - width: 200px; - height: 30px; - border: 1px solid var(--regular-grey-color); - background: var(--tabstyle-bg-color-2); + + .mat-form-field-disabled:hover { + cursor: not-allowed; } - - .mat-form-field-style > { - - /* OVERWRITE MATERIAL INPUT FIELDS */ - .mat-form-field-infix { - color: var(--regular-blue-color); - } - - .mat-select-value { - color: var(--regular-blue-color); - } - - .mat-form-field-label { - color: var(--mat-form-field-label-color) !important; - } - - .mat-form-field-underline { - background-color: var(--mat-form-field-underline-bg-color) !important; - } - - /*Focused: change color of label*/ - .mat-focused .mat-form-field-label { - color: var(--mat-form-field-focused-color) !important; - } - - /*Focused: change color of underline*/ - .mat-form-field-ripple { - background-color: var(--mat-form-field-focused-color) !important;; - } - - .mat-form-field-disabled .mat-form-field-underline - { - background-image: linear-gradient( to right, var(--img-linear-gradient-start-color) 0, var(--textarea-dark-border-color) 10%, var(--img-linear-gradient-color) 0 ) !important; - background-size: 1px 100% !important; - background-repeat: repeat-x !important; - cursor: not-allowed; - } - - .mat-form-field-disabled:hover { - cursor: not-allowed; - } - - mat-hint { - color: var(--regular-red-color) !important; - } - + + mat-hint { + color: var(--regular-red-color) !important; } - +} + .errortext { color: var(--regular-red-color); font-size: 11px; } - + .divclear { - clear: both; + clear: both; } - + .borderbottom { border-bottom: 1px solid var(--regular-gainsboro-color); } @@ -496,7 +499,7 @@ border-top-left-radius: 5px; border-top-right-radius: 5px; } - + .tabStyleHeader { font-size: 14px; font-weight: bold; @@ -507,8 +510,7 @@ padding-right: 5px; } -.panel-style { - +.panel-style { mat-panel-title { width: 300px; min-width: 300px; @@ -527,7 +529,6 @@ } } - :host ::ng-deep app-substance-text-search { .search-button, .close-button, @@ -536,4 +537,9 @@ fill: black !important; } } -} \ No newline at end of file +} + +::ng-deep .search-form-field .mat-mdc-form-field-infix { + padding-top: 24px !important; + padding-bottom: 8px !important; +} diff --git a/src/app/fda/impurities/impurities-form/impurities-test-form/impurities-test-form.component.html b/src/app/fda/impurities/impurities-form/impurities-test-form/impurities-test-form.component.html index ee7bff437..93d25cdea 100644 --- a/src/app/fda/impurities/impurities-form/impurities-test-form/impurities-test-form.component.html +++ b/src/app/fda/impurities/impurities-form/impurities-test-form/impurities-test-form.component.html @@ -1,232 +1,501 @@ -
    +
    -
    Test  {{(impuritiesTestIndex+1)}}
    +
    Test  {{ impuritiesTestIndex + 1 }}
    -
    -
    -
    -
    - + - + - - + + Source ID +
    - - + + Test + - + - - + + Flow Rate +
    - + - - + + - - + + - - + +
    - - + + - - + +
    - + - + - + - - + +
    - - + + - - + name="suitabilityReqRelStandardDeviation" + >
    - - + + - - + +
    - - + + - - + +
    - - + + - - + +
    -
    - +
    - +
    -
    -
    -
    +
    + -
    - -
    -
    +
    +
    - + Solution {{ impuritiesSolution.solutionLetter }} + -
    - +
    - -
    + *ngIf=" + impuritiesTest.elutionType && + (impuritiesTest.elutionType.toUpperCase() === ELUTION_TYPE_ISOCRATIC || + impuritiesTest.elutionType.toUpperCase() === ELUTION_TYPE_GRADIENT) && + impuritiesTest.impuritiesSolutionList.length > 0 + " + > +
    Mobile Phase @@ -235,143 +504,276 @@
     
    -
    -
    +
    + -
    +
    - - - - - + - - - +
    {{columnName}} - + + + + {{ columnName }} +
    - - - + +
    - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + -
    -
    - +
    -
    - +
    +
    - + Impurities            -
    -
    - +
    +
    - -

    +

    - + Unspecified Impurities            - -
    -
    - +
    +
    +
    - -
    \ No newline at end of file +
    diff --git a/src/app/fda/impurities/impurities-form/impurities-total-form/impurities-total-form.component.html b/src/app/fda/impurities/impurities-form/impurities-total-form/impurities-total-form.component.html index 98758ad3e..0512fba7f 100644 --- a/src/app/fda/impurities/impurities-form/impurities-total-form/impurities-total-form.component.html +++ b/src/app/fda/impurities/impurities-form/impurities-total-form/impurities-total-form.component.html @@ -1,29 +1,72 @@
    - - + + Test Type + - + - - + + Limit Value + - - + + Amount Value + - - + + Comments + -
    \ No newline at end of file +
    diff --git a/src/app/fda/impurities/impurities-form/impurities-unspecified-form/impurities-unspecified-form.component.html b/src/app/fda/impurities/impurities-form/impurities-unspecified-form/impurities-unspecified-form.component.html index fbe5e24da..d84363f01 100644 --- a/src/app/fda/impurities/impurities-form/impurities-unspecified-form/impurities-unspecified-form.component.html +++ b/src/app/fda/impurities/impurities-form/impurities-unspecified-form/impurities-unspecified-form.component.html @@ -1,102 +1,215 @@ -

    +
    +
    -
    - + - + - + - - + + Limit Value + - + - - + + Comments + -
    -
    -
    - -
    -
    -
    - +
    - + (valueChange)="identityCriteria.identityCriteriaType = $event" + > - - + + - + -
    -
    -
    -
    - +
    +
    -
    - -
    \ No newline at end of file +
    diff --git a/src/app/fda/substance-details/substance-products/substance-ssg4m/substance-ssg4m.component.html b/src/app/fda/substance-details/substance-products/substance-ssg4m/substance-ssg4m.component.html index af4b77fa5..7d128f5be 100644 --- a/src/app/fda/substance-details/substance-products/substance-ssg4m/substance-ssg4m.component.html +++ b/src/app/fda/substance-details/substance-products/substance-ssg4m/substance-ssg4m.component.html @@ -21,18 +21,23 @@ - + @@ -45,17 +50,19 @@ --> - + @@ -68,10 +75,15 @@ --> - +
    View/Edit View/Edit
    - - Please Login to View/Edit - + Please Login to View/Edit
    Substance Reaction/Role + Substance Reaction/Role +
    -
    +
    - {{ssg4Detail.sbstncReactnSectNm}} + {{ ssg4Detail.sbstncReactnSectNm }} -
    - {{ssg4Detail.sbstncRoleNm}} +
    + {{ ssg4Detail.sbstncRoleNm }}
    - - \ No newline at end of file + + diff --git a/src/styles/_angular-15-mdc-fixes.scss b/src/styles/_angular-15-mdc-fixes.scss index abf38485b..20186ba75 100644 --- a/src/styles/_angular-15-mdc-fixes.scss +++ b/src/styles/_angular-15-mdc-fixes.scss @@ -100,9 +100,9 @@ // MDC form fields have different default appearance and structure .mat-mdc-form-field { // Match Angular 14 form field appearance - font-family: Roboto, "Helvetica Neue", sans-serif; - font-size: 14px; - line-height: 1.125; + font-family: Roboto, "Helvetica Neue", sans-serif !important; + // font-size: 14px !important; + // line-height: 1.125 !important; // Fix the wrapper to not add extra spacing .mat-mdc-text-field-wrapper { @@ -1603,3 +1603,13 @@ a.mat-mdc-outlined-button { padding-bottom: 8px; } } + +// .mat-button, +// .matButton, +// .mat-mdc-button { +// color: var(--link-color) !important; +// } + +// .mat-mdc-button-disabled { +// color: #00000042 !important; +// } From 99a7b712fe9a14310cff600cc33a35003754d77f Mon Sep 17 00:00:00 2001 From: Newatia Date: Thu, 19 Mar 2026 09:23:28 -0400 Subject: [PATCH 335/408] updated IVP --- .../invitro-pharmacology-assay-data-import.component.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/app/fda/invitro-pharmacology/invitro-pharmacology-assay-data-import/invitro-pharmacology-assay-data-import.component.ts b/src/app/fda/invitro-pharmacology/invitro-pharmacology-assay-data-import/invitro-pharmacology-assay-data-import.component.ts index ce8818bf6..52af88508 100644 --- a/src/app/fda/invitro-pharmacology/invitro-pharmacology-assay-data-import/invitro-pharmacology-assay-data-import.component.ts +++ b/src/app/fda/invitro-pharmacology/invitro-pharmacology-assay-data-import/invitro-pharmacology-assay-data-import.component.ts @@ -532,8 +532,8 @@ export class InvitroPharmacologyAssayDataImportComponent implements OnInit { element["targetNameSubstanceUuid"] = substance.uuid; element["targetNameSubstanceKey"] = substanceKey; element["targetNameSubstanceKeyType"] = this.substanceKeyTypeForInvitroPharmacologyConfig; - - if ((element["targetNameApprovalId"]) && (element["targetNameApprovalId"] !== substance.approvalID)) { + console.log(element["targetNameApprovalId"] + " " + substance.approvalID); + if ((element["targetNameApprovalId"]) && (element["targetNameApprovalId"].trim() !== substance.approvalID)) { this.setValidationMessage(this.TARGET_NAME + ' Approval ID "' + element["targetNameApprovalId"] + '" in Excel file does not match with Approval ID "' + substance.approvalID + '" for "' + ingredientName + '" in the database. Please fix in the Excel file and then import again', validationMessages, index); } } @@ -541,7 +541,7 @@ export class InvitroPharmacologyAssayDataImportComponent implements OnInit { element["humanHomologTargetSubstanceKey"] = substanceKey; element["humanHomologTargetSubstanceKeyType"] = this.substanceKeyTypeForInvitroPharmacologyConfig; - if ((element["humanHomologTargetApprovalId"]) && (element["humanHomologTargetApprovalId"] !== substance.approvalID)) { + if ((element["humanHomologTargetApprovalId"]) && (element["humanHomologTargetApprovalId"].trim() !== substance.approvalID)) { this.setValidationMessage(this.HUMAN_HOMOLOG_TARGET + ' Approval ID "' + element["humanHomologTargetApprovalId"] + '" in Excel file does not match with Approval ID "' + substance.approvalID + '" for "' + ingredientName + '" in the database. Please fix in the Excel file and then import again', validationMessages, index); } } @@ -549,7 +549,7 @@ export class InvitroPharmacologyAssayDataImportComponent implements OnInit { element["ligandSubstrateSubstanceKey"] = substanceKey; element["ligandSubstrateSubstanceKeyType"] = this.substanceKeyTypeForInvitroPharmacologyConfig; - if ((element["ligandSubstrateApprovalId"]) && (element["ligandSubstrateApprovalId"] !== substance.approvalID)) { + if ((element["ligandSubstrateApprovalId"]) && (element["ligandSubstrateApprovalId"].trim() !== substance.approvalID)) { this.setValidationMessage(this.LIGAND_SUBSTRATE + ' Approval ID "' + element["ligandSubstrateApprovalId"] + '" in Excel file does not match with Approval ID "' + substance.approvalID + '" for "' + ingredientName + '" in the database. Please fix in the Excel file and then import again', validationMessages, index); } } From 7c89bea5a648aceb36469a95a6c7438fbcc607d0 Mon Sep 17 00:00:00 2001 From: Iaroslav Moskviak Date: Thu, 19 Mar 2026 23:32:20 -0400 Subject: [PATCH 336/408] register forms fixes --- angular.json | 21 +- src/app/core/admin/admin.component.html | 169 ++-- src/app/core/admin/admin.component.scss | 42 +- .../cv-management.component.html | 7 +- .../cv-management.component.scss | 4 +- .../cv-term-dialog.component.html | 56 +- .../data-management.component.scss | 75 +- .../import-browse.component.scss | 2 +- .../import-management.component.scss | 75 +- .../core/admin/monitor/monitor.component.scss | 75 +- .../user-edit-dialog.component.scss | 102 +-- .../user-management.component.scss | 4 +- src/app/core/base/base.component.scss | 23 +- src/app/core/base/base.component.ts | 541 +++++++------ .../file-upload-form.component.scss | 75 +- .../facets-manager.component.scss | 225 +++--- src/app/core/home/home.component.html | 14 +- src/app/core/home/home.component.scss | 19 +- src/app/core/home/home.component.ts | 207 ++--- .../name-resolver.component.html | 4 +- .../name-resolver.component.scss | 12 +- .../structure-editor.component.html | 90 ++- .../structure-image-modal.component.scss | 12 +- .../structure-details.component.scss | 2 +- .../substance-codes.component.html | 2 +- .../substance-names.component.html | 2 +- .../substance-names.component.ts | 12 +- .../substance-references.component.html | 2 +- .../agent-modification-form.component.scss | 1 + .../amount-form-dialog.component.html | 11 +- .../amount-form/amount-form.component.scss | 91 ++- .../substance-form-codes-card.component.html | 8 +- .../cv-input/cv-input.component.html | 62 +- .../cv-input/cv-input.component.scss | 10 +- .../names/name-form.component.scss | 20 +- ...sical-parameter-form-dialog.component.html | 3 +- .../physical-parameter-form.component.scss | 4 +- .../properties/property-form.component.scss | 11 +- .../property-parameter-form.component.html | 12 +- .../apply-reference.component.scss | 6 +- .../references/reference-form.component.html | 11 +- .../references/reference-form.component.scss | 102 ++- .../refernce-form-dialog.component.html | 3 +- .../simplified-code-form.component.html | 19 +- .../simplified-code-form.component.scss | 94 +-- .../simplified-name-form.component.html | 40 +- .../simplified-reference-form.component.html | 29 +- .../simplified-reference-form.component.scss | 10 +- .../substance-form-definition.component.scss | 1 + .../substance-form-subunits.component.html | 82 +- .../subunit-selector-dialog.component.html | 34 +- .../subunit-selector-dialog.component.scss | 11 + .../subunit-selector.component.html | 2 +- .../subunit-selector.component.scss | 158 ++-- .../tag-selector/tag-selector.component.scss | 2 +- .../advanced-selector-dialog.component.html | 32 +- .../advanced-selector-dialog.component.scss | 27 +- .../advanced-selector-dialog.component.ts | 4 +- .../substance-selector.component.html | 153 ++-- .../ssg2-manufacturing.component.html | 2 +- .../ssg2-manufacturing.component.scss | 37 +- .../ssg2-overview-form.component.html | 32 +- .../ssg2-overview-form.component.scss | 27 +- ...g4m-critical-parameter-form.component.scss | 19 +- .../ssg4m-process-form.component.scss | 26 +- .../ssg4m-sites/ssg4m-sites.component.html | 255 +++++-- .../ssg4m-sites/ssg4m-sites.component.scss | 18 +- .../ssg4m-stages-form.component.html | 311 ++++++-- .../ssg4m-stages-form.component.scss | 28 +- .../ssg4m-step-view-dialog.component.scss | 19 +- .../substance-text-search.component.scss | 2 +- .../custom-multiselect-widget.component.scss | 15 - .../custom-checkbox-widget.component.scss | 15 - .../custom-radio-widget.component.scss | 15 - .../custom-select-widget.component.scss | 15 - .../substance-summary-card.component.html | 721 +++++++++++++----- .../substance-summary-card.component.scss | 10 + .../substances-browse.component.scss | 4 +- .../advanced-query-statement.component.html | 286 ++++--- .../advanced-query-statement.component.scss | 9 +- .../adverse-events-cvm-browse.component.scss | 5 +- .../adverse-events-dme-browse.component.scss | 5 +- .../adverse-events-pt-browse.component.scss | 4 +- .../application-form.component.scss | 74 +- .../application-product-form.component.scss | 16 +- .../applications-browse.component.scss | 7 +- .../clinical-trials-browse.component.scss | 6 +- .../cross-entity-search.component.scss | 1 + .../impurities-details-form.component.scss | 56 +- .../impurities-form.component.scss | 77 +- .../impurities-inorganic-form.component.scss | 59 +- ...urities-inorganic-form-test.component.scss | 194 ++--- ...ties-residual-solvents-form.component.scss | 55 +- ...ties-residual-solvents-test.component.scss | 588 +++++++------- .../impurities-substance-form.component.scss | 52 +- .../impurities-test-form.component.scss | 1 + .../impurities-total-form.component.scss | 47 -- ...impurities-unspecified-form.component.scss | 106 +-- ...invitro-pharmacology-browse.component.scss | 5 +- .../product-details.component.scss | 1 - .../products-browse.component.scss | 2 +- src/styles/_base.scss | 17 +- src/styles/_card.scss | 2 +- ...dc-fixes.scss => _material-overrides.scss} | 102 ++- src/styles/_misc.scss | 33 +- src/styles/_table.scss | 10 +- src/styles/main.scss | 28 +- 107 files changed, 3379 insertions(+), 2967 deletions(-) rename src/styles/{_angular-15-mdc-fixes.scss => _material-overrides.scss} (90%) diff --git a/angular.json b/angular.json index e0fdd76da..0c23fc3c4 100644 --- a/angular.json +++ b/angular.json @@ -287,22 +287,16 @@ { "input": "node_modules/@angular/material/prebuilt-themes/indigo-pink.css" }, - "src/styles.scss" + "src/styles/main.scss" ], "scripts": [], - "assets": [ - "src/favicon.ico", - "src/app/core/assets" - ] + "assets": ["src/favicon.ico", "src/app/core/assets"] } }, "lint": { "builder": "@angular-eslint/builder:lint", "options": { - "lintFilePatterns": [ - "src/**/*.ts", - "src/**/*.html" - ] + "lintFilePatterns": ["src/**/*.ts", "src/**/*.html"] } }, "server": { @@ -334,10 +328,7 @@ "lint": { "builder": "@angular-eslint/builder:lint", "options": { - "lintFilePatterns": [ - "e2e//**/*.ts", - "e2e//**/*.html" - ] + "lintFilePatterns": ["e2e//**/*.ts", "e2e//**/*.html"] } } } @@ -448,9 +439,7 @@ } }, "cli": { - "schematicCollections": [ - "@angular-eslint/schematics" - ], + "schematicCollections": ["@angular-eslint/schematics"], "analytics": false }, "schematics": { diff --git a/src/app/core/admin/admin.component.html b/src/app/core/admin/admin.component.html index 46cfb0311..170ff6e82 100644 --- a/src/app/core/admin/admin.component.html +++ b/src/app/core/admin/admin.component.html @@ -1,93 +1,80 @@ -
    -
    - - - - Server Status - -

    Server Status

    -
    - -
    -
    +
    +
    + + + Server Status +

    Server Status

    +
    + +
    +
    - - - Service Information - - -

    Service Information

    - - - -
    -
    + + Service Information + +

    Service Information

    + + + +
    +
    - - - - User Management - - -

    User Management

    - - - -
    -
    - - - Data Import - - -

    Staged Data Import

    - - - -
    -
    - - - CV Management - -

    Controlled Vocabulary Edit

    - - - -
    - - - Scheduled Jobs - - -

    Scheduled Jobs

    -
    - -
    -
    -
    - - - All Files - - -

    All Files

    - - - -
    -
    - - - Data Management (Legacy) - -

    Bulk Data Import

    - - - -
    - -
    - -
    -
    \ No newline at end of file + + User Management + +

    User Management

    + + + +
    +
    + + Data Import + +

    Staged Data Import

    + + + +
    +
    + + CV Management +

    Controlled Vocabulary Edit

    + + + +
    + + Scheduled Jobs + +

    Scheduled Jobs

    +
    + +
    +
    +
    + + All Files + +

    All Files

    + + + +
    +
    + + Data Management (Legacy) +

    Bulk Data Import

    + + + +
    +
    +
    +
    diff --git a/src/app/core/admin/admin.component.scss b/src/app/core/admin/admin.component.scss index 0e1f08788..e008b6ec5 100644 --- a/src/app/core/admin/admin.component.scss +++ b/src/app/core/admin/admin.component.scss @@ -1,40 +1,36 @@ .admin-container { - padding-top:65px; - width:100%; + padding-top: 65px; + width: 100%; } .page-container { - align-items: center; - justify-content: center; - padding: 65px 5px 0 5px; + align-items: center; + justify-content: center; + padding: 65px 5px 0 5px; } - .app-container { - display: flex; - align-items: center; - justify-content: center; - margin: auto; - margin-top: 15px; - max-width: 1500px; - min-width: 1000px; - margin-bottom: 10px; + display: flex; + align-items: center; + justify-content: center; + margin: auto; + margin-top: 15px; + max-width: 1500px; + min-width: 1000px; + margin-bottom: 10px; } .full-width { - width: 100%; + width: 100%; } .tab-label { - margin-left: 75px; - + margin-left: 75px; + font-size: 18px; } -::ng-deep .mat-tab-label { - font-size: 18px !important; -} .admin-header { - font-size:18px; - font-weight: 400; -} \ No newline at end of file + font-size: 18px; + font-weight: 400; +} diff --git a/src/app/core/admin/cv-management/cv-management.component.html b/src/app/core/admin/cv-management/cv-management.component.html index b336bd8b0..7c6c2572b 100644 --- a/src/app/core/admin/cv-management/cv-management.component.html +++ b/src/app/core/admin/cv-management/cv-management.component.html @@ -79,12 +79,7 @@ Edit
    - +
    diff --git a/src/app/core/admin/cv-management/cv-management.component.scss b/src/app/core/admin/cv-management/cv-management.component.scss index caba0d15f..3cf0f60fc 100644 --- a/src/app/core/admin/cv-management/cv-management.component.scss +++ b/src/app/core/admin/cv-management/cv-management.component.scss @@ -39,12 +39,12 @@ max-height: 90%; } -::ng-deep .mat-dialog-container { +::ng-deep .mat-mdc-dialog-container { height: unset !important; max-height: 90%; } -.mat-dialog-content { +.mat-mdc-dialog-content { max-height: 95%; } diff --git a/src/app/core/admin/cv-management/cv-term-dialog/cv-term-dialog.component.html b/src/app/core/admin/cv-management/cv-term-dialog/cv-term-dialog.component.html index 545319f7e..8e4681640 100644 --- a/src/app/core/admin/cv-management/cv-term-dialog/cv-term-dialog.component.html +++ b/src/app/core/admin/cv-management/cv-term-dialog/cv-term-dialog.component.html @@ -29,40 +29,22 @@

    Add Term to CV - {{ vocabulary.domain }}

    - + Display + - + Value + - + Description + - + Origin +
    @@ -81,7 +63,7 @@

    Add Term to CV - {{ vocabulary.domain }}

    'ix.ginas.models.v1.FragmentControlledVocabulary' " > - @@ -96,18 +78,14 @@

    Add Term to CV - {{ vocabulary.domain }}

    " > - + Format Regex + + System Category @@ -123,9 +101,9 @@

    Add Term to CV - {{ vocabulary.domain }}

    + Simplified Structure Add Term to CV - {{ vocabulary.domain }}

    + Fragment Structure Add Term to CV - {{ vocabulary.domain }} >{{ message.message }}
    - + - - + + diff --git a/src/app/core/admin/data-management/data-management.component.scss b/src/app/core/admin/data-management/data-management.component.scss index a7901fc8b..d843e8721 100644 --- a/src/app/core/admin/data-management/data-management.component.scss +++ b/src/app/core/admin/data-management/data-management.component.scss @@ -34,24 +34,13 @@ } .load-fail { - // transform: rotate(180deg); margin-top: -22px; - ::ng-deep .mat-progress-bar-fill { - background-color: var(--error); - z-index: 1; - - } - ::ng-deep .mat-progress-bar-fill::after { - background-color: var(--error); - // z-index: 1; - + mat-progress-bar { + --mdc-linear-progress-active-indicator-color: var(--error); + --mdc-linear-progress-track-color: var(--progress-bar-buffer-bg-color); } - - - - - ::ng-deep .mat-progress-bar-buffer { - background: var(--progress-bar-buffer-bg-color); + ::ng-deep .mdc-linear-progress__primary-bar { + z-index: 1; } } @@ -61,60 +50,34 @@ margin: auto; } } + .load-fail-old { - transform: rotate(180deg); + transform: rotate(180deg); margin-top: -22px; - ::ng-deep .mat-progress-bar-fill { - background-color: var(--error); - z-index: 2; - - + mat-progress-bar { + --mdc-linear-progress-active-indicator-color: var(--error); + --mdc-linear-progress-track-color: var(--progress-bar-buffer-bg-color); } - ::ng-deep .mat-progress-bar-fill::after { - background-color: var(--error); + ::ng-deep .mdc-linear-progress__primary-bar { z-index: 2; - - - } - - - - - ::ng-deep .mat-progress-bar-buffer { - background: var(--progress-bar-buffer-bg-color); } } .load-success { - ::ng-deep .mat-progress-bar-fill { - // background-color: var(--error); - z-index: 2; - - } - ::ng-deep .mat-progress-bar-fill::after { - // background-color: var(--error); - // z-index: 2; - + mat-progress-bar { + --mdc-linear-progress-track-color: var(--progress-bar-buffer-bg-color); } - ::ng-deep .mat-progress-bar-buffer { - background: var(--progress-bar-buffer-bg-color); + ::ng-deep .mdc-linear-progress__primary-bar { + z-index: 2; } } - .load-success-old { - ::ng-deep .mat-progress-bar-fill { - // background-color: var(--error); - z-index: 2; - + mat-progress-bar { + --mdc-linear-progress-track-color: var(--progress-bar-buffer-bg-color); } - ::ng-deep .mat-progress-bar-fill::after { - // background-color: var(--error); - z-index: 2; - - } - ::ng-deep .mat-progress-bar-buffer { - background: var(--progress-bar-buffer-bg-color); + ::ng-deep .mdc-linear-progress__primary-bar { + z-index: 2; } } diff --git a/src/app/core/admin/import-browse/import-browse.component.scss b/src/app/core/admin/import-browse/import-browse.component.scss index 073b6144a..38d995599 100644 --- a/src/app/core/admin/import-browse/import-browse.component.scss +++ b/src/app/core/admin/import-browse/import-browse.component.scss @@ -654,7 +654,7 @@ } } -// Page label and page selector styles moved to global _angular-15-mdc-fixes.scss +// Page label and page selector styles moved to global _material-overrides.scss :host ::ng-deep .mat-paginator-range-label{ diff --git a/src/app/core/admin/import-management/import-management.component.scss b/src/app/core/admin/import-management/import-management.component.scss index fe7f1aa28..22e865abc 100644 --- a/src/app/core/admin/import-management/import-management.component.scss +++ b/src/app/core/admin/import-management/import-management.component.scss @@ -167,24 +167,13 @@ width: 200px; } .load-fail { - // transform: rotate(180deg); margin-top: -22px; - ::ng-deep .mat-progress-bar-fill { - background-color: var(--error); - z-index: 1; - - } - ::ng-deep .mat-progress-bar-fill::after { - background-color: var(--error); - // z-index: 1; - + mat-progress-bar { + --mdc-linear-progress-active-indicator-color: var(--error); + --mdc-linear-progress-track-color: var(--progress-bar-buffer-bg-color); } - - - - - ::ng-deep .mat-progress-bar-buffer { - background: var(--progress-bar-buffer-bg-color); + ::ng-deep .mdc-linear-progress__primary-bar { + z-index: 1; } } @@ -194,60 +183,34 @@ width: 200px; margin: auto; } } + .load-fail-old { - transform: rotate(180deg); + transform: rotate(180deg); margin-top: -22px; - ::ng-deep .mat-progress-bar-fill { - background-color: var(--error); - z-index: 2; - - + mat-progress-bar { + --mdc-linear-progress-active-indicator-color: var(--error); + --mdc-linear-progress-track-color: var(--progress-bar-buffer-bg-color); } - ::ng-deep .mat-progress-bar-fill::after { - background-color: var(--error); + ::ng-deep .mdc-linear-progress__primary-bar { z-index: 2; - - - } - - - - - ::ng-deep .mat-progress-bar-buffer { - background: var(--progress-bar-buffer-bg-color); } } .load-success { - ::ng-deep .mat-progress-bar-fill { - // background-color: var(--error); - z-index: 2; - - } - ::ng-deep .mat-progress-bar-fill::after { - // background-color: var(--error); - // z-index: 2; - + mat-progress-bar { + --mdc-linear-progress-track-color: var(--progress-bar-buffer-bg-color); } - ::ng-deep .mat-progress-bar-buffer { - background: var(--progress-bar-buffer-bg-color); + ::ng-deep .mdc-linear-progress__primary-bar { + z-index: 2; } } - .load-success-old { - ::ng-deep .mat-progress-bar-fill { - // background-color: var(--error); - z-index: 2; - + mat-progress-bar { + --mdc-linear-progress-track-color: var(--progress-bar-buffer-bg-color); } - ::ng-deep .mat-progress-bar-fill::after { - // background-color: var(--error); - z-index: 2; - - } - ::ng-deep .mat-progress-bar-buffer { - background: var(--progress-bar-buffer-bg-color); + ::ng-deep .mdc-linear-progress__primary-bar { + z-index: 2; } } diff --git a/src/app/core/admin/monitor/monitor.component.scss b/src/app/core/admin/monitor/monitor.component.scss index 042d37e93..fe499e067 100644 --- a/src/app/core/admin/monitor/monitor.component.scss +++ b/src/app/core/admin/monitor/monitor.component.scss @@ -38,24 +38,13 @@ } .load-fail { - // transform: rotate(180deg); margin-top: -22px; - ::ng-deep .mat-progress-bar-fill { - background-color: var(--error); - z-index: 1; - - } - ::ng-deep .mat-progress-bar-fill::after { - background-color: var(--error); - // z-index: 1; - + mat-progress-bar { + --mdc-linear-progress-active-indicator-color: var(--error); + --mdc-linear-progress-track-color: var(--progress-bar-buffer-bg-color); } - - - - - ::ng-deep .mat-progress-bar-buffer { - background: var(--progress-bar-buffer-bg-color); + ::ng-deep .mdc-linear-progress__primary-bar { + z-index: 1; } } @@ -63,60 +52,34 @@ width: 70px; margin: auto; } + .load-fail-old { - transform: rotate(180deg); + transform: rotate(180deg); margin-top: -22px; - ::ng-deep .mat-progress-bar-fill { - background-color: var(--error); - z-index: 2; - - + mat-progress-bar { + --mdc-linear-progress-active-indicator-color: var(--error); + --mdc-linear-progress-track-color: var(--progress-bar-buffer-bg-color); } - ::ng-deep .mat-progress-bar-fill::after { - background-color: var(--error); + ::ng-deep .mdc-linear-progress__primary-bar { z-index: 2; - - - } - - - - - ::ng-deep .mat-progress-bar-buffer { - background: var(--progress-bar-buffer-bg-color); } } .load-success { - ::ng-deep .mat-progress-bar-fill { - // background-color: var(--error); - z-index: 2; - - } - ::ng-deep .mat-progress-bar-fill::after { - // background-color: var(--error); - // z-index: 2; - + mat-progress-bar { + --mdc-linear-progress-track-color: var(--progress-bar-buffer-bg-color); } - ::ng-deep .mat-progress-bar-buffer { - background: var(--progress-bar-buffer-bg-color); + ::ng-deep .mdc-linear-progress__primary-bar { + z-index: 2; } } - .load-success-old { - ::ng-deep .mat-progress-bar-fill { - // background-color: var(--error); - z-index: 2; - + mat-progress-bar { + --mdc-linear-progress-track-color: var(--progress-bar-buffer-bg-color); } - ::ng-deep .mat-progress-bar-fill::after { - // background-color: var(--error); - z-index: 2; - - } - ::ng-deep .mat-progress-bar-buffer { - background: var(--progress-bar-buffer-bg-color); + ::ng-deep .mdc-linear-progress__primary-bar { + z-index: 2; } } diff --git a/src/app/core/admin/user-management/user-edit-dialog/user-edit-dialog.component.scss b/src/app/core/admin/user-management/user-edit-dialog/user-edit-dialog.component.scss index 96ab6f836..2c4b0a705 100644 --- a/src/app/core/admin/user-management/user-edit-dialog/user-edit-dialog.component.scss +++ b/src/app/core/admin/user-management/user-edit-dialog/user-edit-dialog.component.scss @@ -1,95 +1,103 @@ .user-container { - display:flex; - flex-direction: row; + display: flex; + flex-direction: row; } .password-column { - display: flex; - flex-direction: column; + display: flex; + flex-direction: column; } .loading-container { - height:200px; - margin: auto; + height: 200px; + margin: auto; } .form-row { - display:flex; - width: 100%; - flex-direction: row; - margin-bottom: 15px; - - .username { - width: 35%; - } - .email { - width: 45%; - } - .active { - width: 20%; - } + display: flex; + width: 100%; + flex-direction: row; + margin-bottom: 15px; + + .username { + width: 35%; + } + .email { + width: 45%; + } + .active { + width: 20%; + } } .user-field { - width: 90%; + width: 90%; } .groups-group { - width: 70%; + width: 70%; } .roles-group { - width: 30%; + width: 30%; } .group-field { - width: 90%; - margin-top: 5px; + width: 90%; + margin-top: 5px; } ::ng-deep .mat-dialog-content { - padding: 10px 24px !important; + padding: 10px 24px !important; } .message { - margin: auto; - text-align: center; - padding: 10px; + margin: auto; + text-align: center; + padding: 10px; } .error-msg { - background: var(--regular-red-color); - color: var(--regular-white-color); - border: 1px solid var(--regular-black-color); + background: var(--regular-red-color); + color: var(--regular-white-color); + border: 1px solid var(--regular-black-color); } .box-label { - padding-bottom: 10px; + padding-bottom: 10px; } -::ng-deep .mat-dialog-container { - height: unset !important; - max-height: 90%; +::ng-deep .mat-mdc-dialog-container { + height: unset !important; + max-height: 90%; } -::ng-deep .mat-checkbox-label { - word-break: break-word !important; - white-space: initial !important; +::ng-deep .mdc-label { + word-break: break-word !important; + white-space: initial !important; } .group-checkbox { - padding-bottom: 4px; + margin-bottom: -8px; + + mat-checkbox { + --mdc-checkbox-state-layer-size: 32px; + } } .submitted-container { - height: 200px; - vertical-align: middle; - text-align: center; - display: flex; - + height: 200px; + vertical-align: middle; + text-align: center; + display: flex; } .submit-message { - margin: auto; - font-size: 20px; + margin: auto; + font-size: 20px; +} + +.title { + font-size: 14px; + font-weight: 500; } diff --git a/src/app/core/admin/user-management/user-management.component.scss b/src/app/core/admin/user-management/user-management.component.scss index 3787960cb..cc614e1b6 100644 --- a/src/app/core/admin/user-management/user-management.component.scss +++ b/src/app/core/admin/user-management/user-management.component.scss @@ -67,8 +67,8 @@ padding: 0 12px; min-width: 64px; box-sizing: border-box; - font-weight: 600; - font-size: 16px; + font-weight: 500; + font-size: 14px; white-space: nowrap; } } diff --git a/src/app/core/base/base.component.scss b/src/app/core/base/base.component.scss index 85e9b918a..75889abb9 100644 --- a/src/app/core/base/base.component.scss +++ b/src/app/core/base/base.component.scss @@ -33,6 +33,8 @@ } button.mat-mdc-button.top-button { + --button-icon-svg-size: 100%; + font-size: 16px; color: white; padding-right: 10px; @@ -80,10 +82,10 @@ button.mat-mdc-button.top-button { width: 25px !important; } } -:host ::ng-deep .mat-icon { - height: 30px !important; - width: 30px !important; - margin-bottom: -10px !important; +.logged-in .user-actions .mat-icon { + height: 30px; + width: 30px; + margin-bottom: -10px; } .mat-icon .user-icon { @@ -179,7 +181,7 @@ button.mat-mdc-button.top-button { .nav-small { padding-right: 10px; - .mat-icon-button { + button[mat-icon-button] { width: 50px; height: 50px; } @@ -199,13 +201,6 @@ button.mat-mdc-button.top-button { .top-search { flex-grow: 1; max-width: 600px; - - ::ng-deep .mat-form-field { - .mat-form-field-label { - font-size: 16px; - top: 1.98125em; - } - } } ::ng-deep .transparent-background { @@ -214,10 +209,6 @@ button.mat-mdc-button.top-button { .classic-view-container { padding-left: 15px; - - ::ng-deep .mat-raised-button { - color: #296ca3; - } } // @media(max-width: $nav-breaking-point) { diff --git a/src/app/core/base/base.component.ts b/src/app/core/base/base.component.ts index 5f4eb1d4f..1e4b51bd7 100644 --- a/src/app/core/base/base.component.ts +++ b/src/app/core/base/base.component.ts @@ -1,38 +1,55 @@ -import { Component, OnInit, ViewEncapsulation, HostListener, OnDestroy } from '@angular/core'; -import { Router, Event, NavigationExtras, ActivatedRoute, NavigationStart, ResolveEnd, ParamMap } from '@angular/router'; -import { Environment } from '../../../environments/environment.model'; -import { AuthService } from '../auth/auth.service'; -import { Auth } from '../auth/auth.model'; -import { SessionExpirationComponent } from '../auth/session-expiration/session-expiration.component' -import { ConfigService } from '../config/config.service'; -import { OverlayContainer } from '@angular/cdk/overlay'; -import { LoadingService } from '../loading/loading.service'; -import { HighlightedSearchActionComponent } from '../highlighted-search-action/highlighted-search-action.component'; -import { MatDialog } from '@angular/material/dialog'; -import { MatBottomSheet, MatBottomSheetRef } from '@angular/material/bottom-sheet'; -import { Observable, Subscription } from 'rxjs'; -import { UserProfileComponent } from '@gsrs-core/auth/user-profile/user-profile.component'; -import { SubstanceTextSearchService } from '@gsrs-core/substance-text-search/substance-text-search.service'; -import { NavItem, LoadedComponents } from '../config/config.model'; -import { UtilsService } from '@gsrs-core/utils'; -import { take } from 'rxjs/operators'; -import * as moment from 'moment'; -import { SubstanceEditImportDialogComponent } from '@gsrs-core/substance-edit-import-dialog/substance-edit-import-dialog.component'; -import { WildcardService } from '@gsrs-core/utils/wildcard.service'; -import { SubstanceDraftsComponent } from '@gsrs-core/substance-form/substance-drafts/substance-drafts.component'; -import {sprintf} from "sprintf-js"; -import { BulkSearchService } from '@gsrs-core/bulk-search/service/bulk-search.service'; -import { UserQueryListDialogComponent } from '@gsrs-core/bulk-search/user-query-list-dialog/user-query-list-dialog.component'; +import { + Component, + OnInit, + ViewEncapsulation, + HostListener, + OnDestroy, +} from "@angular/core"; +import { + Router, + Event, + NavigationExtras, + ActivatedRoute, + NavigationStart, + ResolveEnd, + ParamMap, +} from "@angular/router"; +import { Environment } from "../../../environments/environment.model"; +import { AuthService } from "../auth/auth.service"; +import { Auth } from "../auth/auth.model"; +import { SessionExpirationComponent } from "../auth/session-expiration/session-expiration.component"; +import { ConfigService } from "../config/config.service"; +import { OverlayContainer } from "@angular/cdk/overlay"; +import { LoadingService } from "../loading/loading.service"; +import { HighlightedSearchActionComponent } from "../highlighted-search-action/highlighted-search-action.component"; +import { MatDialog } from "@angular/material/dialog"; +import { + MatBottomSheet, + MatBottomSheetRef, +} from "@angular/material/bottom-sheet"; +import { Observable, Subscription } from "rxjs"; +import { UserProfileComponent } from "@gsrs-core/auth/user-profile/user-profile.component"; +import { SubstanceTextSearchService } from "@gsrs-core/substance-text-search/substance-text-search.service"; +import { NavItem, LoadedComponents } from "../config/config.model"; +import { UtilsService } from "@gsrs-core/utils"; +import { take } from "rxjs/operators"; +import * as moment from "moment"; +import { SubstanceEditImportDialogComponent } from "@gsrs-core/substance-edit-import-dialog/substance-edit-import-dialog.component"; +import { WildcardService } from "@gsrs-core/utils/wildcard.service"; +import { SubstanceDraftsComponent } from "@gsrs-core/substance-form/substance-drafts/substance-drafts.component"; +import { sprintf } from "sprintf-js"; +import { BulkSearchService } from "@gsrs-core/bulk-search/service/bulk-search.service"; +import { UserQueryListDialogComponent } from "@gsrs-core/bulk-search/user-query-list-dialog/user-query-list-dialog.component"; @Component({ - selector: 'app-base', - templateUrl: './base.component.html', - styleUrls: ['./base.component.scss'], - encapsulation: ViewEncapsulation.None, - standalone: false + selector: "app-base", + templateUrl: "./base.component.html", + styleUrls: ["./base.component.scss"], + encapsulation: ViewEncapsulation.None, + standalone: false, }) export class BaseComponent implements OnInit, OnDestroy { - mainPathSegment = ''; + mainPathSegment = ""; logoSrcPath: string; auth?: Auth; environment: Environment; @@ -45,11 +62,11 @@ export class BaseComponent implements OnInit, OnDestroy { canManageCVs: boolean = false; contactEmail: string; version?: string; - versionTooltipMessage = ''; + versionTooltipMessage = ""; appId: string; clasicBaseHref: string; navItems: Array; - customToolbarComponent: string = ''; + customToolbarComponent: string = ""; canRegister = false; registerNav: Array; searchNav: Array; @@ -63,7 +80,7 @@ export class BaseComponent implements OnInit, OnDestroy { private subscriptions: Array = []; private wildCardText: string; private classicLinkQueryParams = {}; - showHeaderBar = 'true'; + showHeaderBar = "true"; constructor( private router: Router, @@ -76,31 +93,34 @@ export class BaseComponent implements OnInit, OnDestroy { private dialog: MatDialog, private substanceTextSearchService: SubstanceTextSearchService, private utilsService: UtilsService, - private wildCardService: WildcardService + private wildCardService: WildcardService, ) { - this.customToolbarComponent = this.configService.configData.customToolbarComponent; + this.customToolbarComponent = + this.configService.configData.customToolbarComponent; this.wildCardService.wildCardObservable.subscribe((data) => { this.wildCardText = data; }); } - @HostListener('document:mouseup', ['$event']) - @HostListener('document:keyup', ['$event']) + @HostListener("document:mouseup", ["$event"]) + @HostListener("document:keyup", ["$event"]) // @HostListener('document:selectionchange', ['$event']) onKeyUp(event: Event) { - let text = ''; + let text = ""; let selection: Selection; let range: Range; let selectionStart: number; let selectionEnd: number; - const activeEl: HTMLInputElement = document.activeElement as HTMLInputElement; + const activeEl: HTMLInputElement = + document.activeElement as HTMLInputElement; if (activeEl != null) { const activeElTagName = activeEl ? activeEl.tagName.toLowerCase() : null; if ( - (activeElTagName === 'textarea') || (activeElTagName === 'input' && - /^(?:text|search|password|tel|url)$/i.test(activeEl.type)) && - (typeof activeEl.selectionStart === 'number') + activeElTagName === "textarea" || + (activeElTagName === "input" && + /^(?:text|search|password|tel|url)$/i.test(activeEl.type) && + typeof activeEl.selectionStart === "number") ) { selectionStart = activeEl.selectionStart; selectionEnd = activeEl.selectionEnd; @@ -123,18 +143,20 @@ export class BaseComponent implements OnInit, OnDestroy { } async ngOnInit() { - this.showHeaderBar = this.activatedRoute.snapshot.queryParams['header'] || 'true'; - this.loadedComponents = this.configService.configData.loadedComponents || null; + this.showHeaderBar = + this.activatedRoute.snapshot.queryParams["header"] || "true"; + this.loadedComponents = + this.configService.configData.loadedComponents || null; this.classicLinkPath = this.configService.environment.clasicBaseHref; this.clasicBaseHref = this.configService.environment.clasicBaseHref; - this.classicLinkQueryParamsString = ''; + this.classicLinkQueryParamsString = ""; this.contactEmail = this.configService.configData.contactEmail || null; this.navItems = this.configService.configData.navItems || null; // CRITICAL: Subscribe to auth FIRST, before any await calls that might fail // This ensures UI updates reactively when auth state changes - const authSubscription = this.authService.getAuth().subscribe(auth => { + const authSubscription = this.authService.getAuth().subscribe((auth) => { this.auth = auth; // Re-check privileges when auth changes if (auth) { @@ -175,75 +197,93 @@ export class BaseComponent implements OnInit, OnDestroy { // TODO: remove it and test that the component works. this.baseDomain = this.configService.configData.apiUrlDomain; - this.utilsService.getBuildInfo().pipe(take(1)).subscribe(buildInfo => { - this.version = this.configService.configData.version || buildInfo.version; - this.versionTooltipMessage = `V${this.version}`; - this.versionTooltipMessage += ` built on ${moment(new Date(buildInfo.buildTime)).utc().format('ddd MMM D YYYY HH:mm:ss z')}`; - }); + this.utilsService + .getBuildInfo() + .pipe(take(1)) + .subscribe((buildInfo) => { + this.version = + this.configService.configData.version || buildInfo.version; + this.versionTooltipMessage = `V${this.version}`; + this.versionTooltipMessage += ` built on ${moment(new Date(buildInfo.buildTime)).utc().format("ddd MMM D YYYY HH:mm:ss z")}`; + }); let okToRegister: boolean = await this.authService.canEditData(); - if(okToRegister) { - this.navItems.forEach(item => { - if (item.display === 'Register') { - this.registerNav = item.children; - } - if (item.display === 'Search') { - this.searchNav = item.children; - } - }); - if (this.loadedComponents) { - for(let i = this.navItems.length - 1; i >= 0; i--) { - if (this.navItems[i].children) { - for (let j = this.navItems[i].children.length - 1; j >= 0; j--) { - if (this.navItems[i].children[j].component) { - if (!this.loadedComponents[this.navItems[i].children[j].component]) { - this.navItems[i].children.splice(j, 1); - } + if (okToRegister) { + this.navItems.forEach((item) => { + if (item.display === "Register") { + this.registerNav = item.children; } + if (item.display === "Search") { + this.searchNav = item.children; } + }); + if (this.loadedComponents) { + for (let i = this.navItems.length - 1; i >= 0; i--) { + if (this.navItems[i].children) { + for (let j = this.navItems[i].children.length - 1; j >= 0; j--) { + if (this.navItems[i].children[j].component) { + if ( + !this.loadedComponents[this.navItems[i].children[j].component] + ) { + this.navItems[i].children.splice(j, 1); + } + } + } + } + if (this.navItems[i].component) { + if (!this.loadedComponents[this.navItems[i].component]) { + this.navItems.splice(i, 1); + } + } + } + } } - if (this.navItems[i].component) { - if (!this.loadedComponents[this.navItems[i].component]) { - this.navItems.splice(i, 1); - - } - } - } - } -} this.overlayContainer = this.overlayContainerService.getContainerElement(); - let urlPath = this.router.routerState.snapshot.url.split('?')[0]; + let urlPath = this.router.routerState.snapshot.url.split("?")[0]; this.setClassicLinkPath(urlPath.substring(1)); - if (this.activatedRoute.snapshot.queryParamMap.has('search')) { - this.searchValue = this.activatedRoute.snapshot.queryParamMap.get('search'); - this.setClassicLinkQueryParams(this.activatedRoute.snapshot.queryParamMap); + if (this.activatedRoute.snapshot.queryParamMap.has("search")) { + this.searchValue = + this.activatedRoute.snapshot.queryParamMap.get("search"); + this.setClassicLinkQueryParams( + this.activatedRoute.snapshot.queryParamMap, + ); } - const paramsSubscription = this.activatedRoute.queryParamMap.subscribe(params => { - this.searchValue = params.get('search'); - this.setClassicLinkQueryParams(params); - }); + const paramsSubscription = this.activatedRoute.queryParamMap.subscribe( + (params) => { + this.searchValue = params.get("search"); + this.setClassicLinkQueryParams(params); + }, + ); this.subscriptions.push(paramsSubscription); - const authSubscription2 = this.authService.checkAuth().subscribe(_auth => { - }, error => { - if (error.status === 403 && (this.router.url.split('?')[0] !== '/login' && this.router.url.split('?')[0] !== '/unauthorized')) { - this.loadingService.setLoading(false); - this.router.navigate(['/unauthorized']); - } - }); + const authSubscription2 = this.authService.checkAuth().subscribe( + (_auth) => {}, + (error) => { + if ( + error.status === 403 && + this.router.url.split("?")[0] !== "/login" && + this.router.url.split("?")[0] !== "/unauthorized" + ) { + this.loadingService.setLoading(false); + this.router.navigate(["/unauthorized"]); + } + }, + ); this.subscriptions.push(authSubscription2); this.environment = this.configService.environment; this.appId = this.environment.appId; - this.logoSrcPath = `${this.environment.baseHref || ''}assets/images/gsrs-logo.svg`; + this.logoSrcPath = `${this.environment.baseHref || ""}assets/images/gsrs-logo.svg`; const routerSubscription = this.router.events.subscribe((event: Event) => { if (event instanceof ResolveEnd) { - this.mainPathSegment = this.getMainPathSegmentFromUrl(event.url.substring(1)); - urlPath = event.url.split('?')[0]; + this.mainPathSegment = this.getMainPathSegmentFromUrl( + event.url.substring(1), + ); + urlPath = event.url.split("?")[0]; this.setClassicLinkPath(urlPath.substring(1)); } @@ -255,18 +295,23 @@ export class BaseComponent implements OnInit, OnDestroy { this.subscriptions.push(routerSubscription); this.router.routeReuseStrategy.shouldReuseRoute = () => false; - this.mainPathSegment = this.getMainPathSegmentFromUrl(this.router.routerState.snapshot.url.substring(1)); - - this.substanceTextSearchService.registerSearchComponent('main-substance-search'); - const cleanSearchSubscription = this.substanceTextSearchService.setSearchComponentValueEvent('main-substance-search') - .subscribe(value => { - this.searchValue = value; - }); + this.mainPathSegment = this.getMainPathSegmentFromUrl( + this.router.routerState.snapshot.url.substring(1), + ); + + this.substanceTextSearchService.registerSearchComponent( + "main-substance-search", + ); + const cleanSearchSubscription = this.substanceTextSearchService + .setSearchComponentValueEvent("main-substance-search") + .subscribe((value) => { + this.searchValue = value; + }); this.subscriptions.push(cleanSearchSubscription); } ngOnDestroy() { - this.subscriptions.forEach(subscription => { + this.subscriptions.forEach((subscription) => { subscription.unsubscribe(); }); clearTimeout(this.bottomSheetOpenTimer); @@ -274,19 +319,19 @@ export class BaseComponent implements OnInit, OnDestroy { } getMainPathSegmentFromUrl(url: string): string { - const path = url.split('?')[0]; - const mainPathPart = path.split('/')[0]; + const path = url.split("?")[0]; + const mainPathPart = path.split("/")[0]; return mainPathPart; } routeToLogin(): void { const navigationExtras: NavigationExtras = { queryParams: { - path: this.router.url - } + path: this.router.url, + }, }; - this.router.navigate(['/login'], navigationExtras); + this.router.navigate(["/login"], navigationExtras); } processSubstanceSearch(searchValue: string) { @@ -295,15 +340,14 @@ export class BaseComponent implements OnInit, OnDestroy { } navigateToSearchResults(searchTerm: string) { - const navigationExtras: NavigationExtras = { - queryParams: searchTerm ? { search: searchTerm } : null + queryParams: searchTerm ? { search: searchTerm } : null, }; - this.router.navigate(['/browse-substance'], navigationExtras); + this.router.navigate(["/browse-substance"], navigationExtras); } increaseMenuZindex(): void { - this.overlayContainer.style.zIndex = '1001'; + this.overlayContainer.style.zIndex = "1001"; } removeZindex(): void { @@ -311,11 +355,8 @@ export class BaseComponent implements OnInit, OnDestroy { } openSearchBottomSheet(searchTerm: string): Observable { - - return new Observable(observer => { - + return new Observable((observer) => { if (searchTerm) { - clearTimeout(this.bottomSheetCloseTimer); if (this.bottomSheetRef != null) { @@ -323,16 +364,21 @@ export class BaseComponent implements OnInit, OnDestroy { this.bottomSheetRef = null; } - this.bottomSheetRef = this.bottomSheet.open(HighlightedSearchActionComponent, { - data: { searchTerm: searchTerm }, - hasBackdrop: false, - closeOnNavigation: true - }); - - const openedSubscription = this.bottomSheetRef.afterOpened().subscribe(() => { - observer.next(); - openedSubscription.unsubscribe(); - }); + this.bottomSheetRef = this.bottomSheet.open( + HighlightedSearchActionComponent, + { + data: { searchTerm: searchTerm }, + hasBackdrop: false, + closeOnNavigation: true, + }, + ); + + const openedSubscription = this.bottomSheetRef + .afterOpened() + .subscribe(() => { + observer.next(); + openedSubscription.unsubscribe(); + }); this.bottomSheetCloseTimer = setTimeout(() => { if (this.bottomSheetRef != null) { this.bottomSheetRef.dismiss(); @@ -340,12 +386,14 @@ export class BaseComponent implements OnInit, OnDestroy { observer.complete(); } }, 5000); - const dismissedSubscription = this.bottomSheetRef.afterDismissed().subscribe(() => { - clearTimeout(this.bottomSheetCloseTimer); - this.bottomSheetRef = null; - observer.complete(); - dismissedSubscription.unsubscribe(); - }); + const dismissedSubscription = this.bottomSheetRef + .afterDismissed() + .subscribe(() => { + clearTimeout(this.bottomSheetCloseTimer); + this.bottomSheetRef = null; + observer.complete(); + dismissedSubscription.unsubscribe(); + }); } else { observer.error(); observer.complete(); @@ -354,42 +402,42 @@ export class BaseComponent implements OnInit, OnDestroy { } transformMailToPath(item: NavItem) { - if(item?.kind && item?.mailToPath) { - let subject =''; - let email =''; - if(item.kind==='contact-us') { + if (item?.kind && item?.mailToPath) { + let subject = ""; + let email = ""; + if (item.kind === "contact-us") { email = this.contactEmail; } - if(item?.queryParams) { - if(item?.queryParams?.subject) { + if (item?.queryParams) { + if (item?.queryParams?.subject) { subject = item.queryParams.subject; } } const part1 = sprintf(item.mailToPath, email); - let part2 =''; - if(subject) { - part2 = 'subject='+subject; + let part2 = ""; + if (subject) { + part2 = "subject=" + subject; } - return part1+'?'+part2; + return part1 + "?" + part2; } - return ''; + return ""; } setClassicLinkPath(path: string): void { const basePath = this.clasicBaseHref; const pathDictionary = { - '/home': '', - '/browse-substance': 'substances', - '/structure-search': 'structure', - '/sequence-search': 'sequence', - '/substances/register': 'wizard', - '/admin': 'admin' + "/home": "", + "/browse-substance": "substances", + "/structure-search": "structure", + "/sequence-search": "sequence", + "/substances/register": "wizard", + "/admin": "admin", }; - const pathParts = path.split('/'); + const pathParts = path.split("/"); - let pathKey = ''; + let pathKey = ""; pathParts.forEach((part, index) => { if (index < 2) { pathKey += `/${part}`; @@ -397,31 +445,37 @@ export class BaseComponent implements OnInit, OnDestroy { this.setClassicLinkQueryParams(null, { kind: part }); } }); - this.classicLinkPath = `${basePath}${pathDictionary[pathKey] || ''}`; + this.classicLinkPath = `${basePath}${pathDictionary[pathKey] || ""}`; } - setClassicLinkQueryParams(paramMap?: ParamMap, params?: { [queryParam: string]: string }): void { - + setClassicLinkQueryParams( + paramMap?: ParamMap, + params?: { [queryParam: string]: string }, + ): void { if (paramMap != null) { const paramsDict = {}; - paramsDict['q'] = paramMap.get('search') - || paramMap.get('structure_search') - || paramMap.get('sequence_search') - || paramMap.get('structure'); - - if (paramMap.get('sequence_search')) { - paramsDict['type'] = 'sequence'; - paramsDict['identity'] = paramMap.get('cutoff'); - paramsDict['identityType'] = paramMap.get('type'); - } else if (paramMap.get('structure_search') || paramMap.get('structure')) { - paramsDict['cutoff'] = paramMap.get('cutoff'); - paramsDict['type'] = paramMap.get('type'); + paramsDict["q"] = + paramMap.get("search") || + paramMap.get("structure_search") || + paramMap.get("sequence_search") || + paramMap.get("structure"); + + if (paramMap.get("sequence_search")) { + paramsDict["type"] = "sequence"; + paramsDict["identity"] = paramMap.get("cutoff"); + paramsDict["identityType"] = paramMap.get("type"); + } else if ( + paramMap.get("structure_search") || + paramMap.get("structure") + ) { + paramsDict["cutoff"] = paramMap.get("cutoff"); + paramsDict["type"] = paramMap.get("type"); } - paramsDict['id'] = paramMap.get('sequence'); - paramsDict['seqType'] = paramMap.get('seq_type'); + paramsDict["id"] = paramMap.get("sequence"); + paramsDict["seqType"] = paramMap.get("seq_type"); - Object.keys(paramsDict).forEach(key => { + Object.keys(paramsDict).forEach((key) => { if (paramsDict[key] != null) { this.classicLinkQueryParams[key] = paramsDict[key]; } @@ -429,14 +483,14 @@ export class BaseComponent implements OnInit, OnDestroy { } if (params != null) { - Object.keys(params).forEach(key => { + Object.keys(params).forEach((key) => { this.classicLinkQueryParams[key] = params[key]; }); } - let queryParamsString = ''; + let queryParamsString = ""; Object.keys(this.classicLinkQueryParams).forEach((key, index) => { - const separator = index && '&' || '?'; + const separator = (index && "&") || "?"; queryParamsString += `${separator}${key}=${this.classicLinkQueryParams[key]}`; }); @@ -446,96 +500,111 @@ export class BaseComponent implements OnInit, OnDestroy { openProfile(): void { const dialogRef = this.dialog.open(UserProfileComponent, { data: {}, - width: '800px' - }); - this.overlayContainer.style.zIndex = '1002'; - const dialogSubscription = dialogRef.afterClosed().pipe(take(1)).subscribe(response => { - this.overlayContainer.style.zIndex = null; + width: "800px", }); + this.overlayContainer.style.zIndex = "1002"; + const dialogSubscription = dialogRef + .afterClosed() + .pipe(take(1)) + .subscribe((response) => { + this.overlayContainer.style.zIndex = null; + }); } importDialog(): void { const dialogRef = this.dialog.open(SubstanceEditImportDialogComponent, { - width: '650px', - autoFocus: false - - }); - this.overlayContainer.style.zIndex = '1002'; - - const dialogSubscription = dialogRef.afterClosed().pipe(take(1)).subscribe(response => { - if (response) { - this.overlayContainer.style.zIndex = null; - this.router.onSameUrlNavigation = 'reload'; - this.router.navigateByUrl('/substances/register?action=import', { state: { record: response } }); - } + width: "650px", + autoFocus: false, }); - + this.overlayContainer.style.zIndex = "1002"; + + const dialogSubscription = dialogRef + .afterClosed() + .pipe(take(1)) + .subscribe((response) => { + if (response) { + this.overlayContainer.style.zIndex = null; + this.router.onSameUrlNavigation = "reload"; + this.router.navigateByUrl("/substances/register?action=import", { + state: { record: response }, + }); + } + }); } viewLists(list?: string): void { - let data = {view: 'all'}; + let data = { view: "all" }; if (list) { - data.view = 'single'; - data['activeName'] = list.split(':')[1]; + data.view = "single"; + data["activeName"] = list.split(":")[1]; } const dialogRef = this.dialog.open(UserQueryListDialogComponent, { - width: '850px', + width: "850px", autoFocus: false, - data: data - - }); - this.overlayContainer.style.zIndex = '1002'; - - const dialogSubscription = dialogRef.afterClosed().pipe(take(1)).subscribe(response => { - if (response) { - this.overlayContainer.style.zIndex = null; - } + data: data, }); + this.overlayContainer.style.zIndex = "1002"; + + const dialogSubscription = dialogRef + .afterClosed() + .pipe(take(1)) + .subscribe((response) => { + if (response) { + this.overlayContainer.style.zIndex = null; + } + }); } logout() { this.authService.logout(); setTimeout(() => { - if (this.configService.configData && this.configService.configData.logoutRedirectUrl){ + if ( + this.configService.configData && + this.configService.configData.logoutRedirectUrl + ) { window.location.href = this.configService.configData.logoutRedirectUrl; } else { - this.router.navigate(['/home']); + this.router.navigate(["/home"]); } }, 1200); } viewDrafts(): void { const dialogRef = this.dialog.open(SubstanceDraftsComponent, { - maxHeight: '85%', - width: '70%', - data: {view: 'user'} + maxHeight: "85%", + width: "70%", + data: { view: "user" }, }); - this.overlayContainer.style.zIndex = '1002'; + this.overlayContainer.style.zIndex = "1002"; - dialogRef.afterClosed().subscribe(response => { + dialogRef.afterClosed().subscribe((response) => { this.overlayContainer.style.zIndex = null; - if (response) { - this.loadingService.setLoading(true); - - const read = response.substance; - - if (response.uuid && response.uuid != 'register'){ - const url = '/substances/' + response.uuid + '/edit?action=import&source=draft'; - this.router.navigateByUrl(url, { state: { record: response.substance } }); - } else { - setTimeout(() => { - // this.overlayContainer.style.zIndex = null; - this.router.onSameUrlNavigation = 'reload'; - let url = '/substances/register/' + response.substance.substanceClass + '?action=import' - this.router.navigateByUrl(url, { state: { record: response.substance } }); - - }, 500); - } - } - - + this.loadingService.setLoading(true); + + const read = response.substance; + + if (response.uuid && response.uuid != "register") { + const url = + "/substances/" + response.uuid + "/edit?action=import&source=draft"; + this.router.navigateByUrl(url, { + state: { record: response.substance }, + }); + } else { + setTimeout(() => { + // this.overlayContainer.style.zIndex = null; + this.router.onSameUrlNavigation = "reload"; + let url = + "/substances/register/" + + response.substance.substanceClass + + "?action=import"; + this.router.navigateByUrl(url, { + state: { record: response.substance }, + }); + }, 500); + } + } }); } @@ -545,10 +614,13 @@ export class BaseComponent implements OnInit, OnDestroy { */ private async updatePrivileges(): Promise { try { - this.canConfigureSystem = await this.authService.hasSpecificPrivilege('Configure System'); - this.canUserImportData = await this.authService.hasSpecificPrivilege('Import Data'); + this.canConfigureSystem = + await this.authService.hasSpecificPrivilege("Configure System"); + this.canUserImportData = + await this.authService.hasSpecificPrivilege("Import Data"); this.canRegister = await this.authService.canEditData(); - this.canManageCVs = await this.authService.hasSpecificPrivilege('Manage CVs'); + this.canManageCVs = + await this.authService.hasSpecificPrivilege("Manage CVs"); } catch (e) { // Not authenticated or error - all privileges default to false this.canConfigureSystem = false; @@ -557,5 +629,4 @@ export class BaseComponent implements OnInit, OnDestroy { this.canManageCVs = false; } } - } diff --git a/src/app/core/bulk-search/file-upload-form/file-upload-form.component.scss b/src/app/core/bulk-search/file-upload-form/file-upload-form.component.scss index 55a0a7ef5..76777887d 100644 --- a/src/app/core/bulk-search/file-upload-form/file-upload-form.component.scss +++ b/src/app/core/bulk-search/file-upload-form/file-upload-form.component.scss @@ -34,24 +34,13 @@ } .load-fail { - // transform: rotate(180deg); margin-top: -22px; - ::ng-deep .mat-progress-bar-fill { - background-color: rgb(173, 26, 26); - z-index: 1; - - } - ::ng-deep .mat-progress-bar-fill::after { - background-color: rgb(173, 26, 26); - // z-index: 1; - + mat-progress-bar { + --mdc-linear-progress-active-indicator-color: rgb(173, 26, 26); + --mdc-linear-progress-track-color: rgba(0, 0, 0, 0.01); } - - - - - ::ng-deep .mat-progress-bar-buffer { - background: rgba(0, 0, 0, 0.01); + ::ng-deep .mdc-linear-progress__primary-bar { + z-index: 1; } } @@ -61,60 +50,34 @@ margin: auto; } } + .load-fail-old { - transform: rotate(180deg); + transform: rotate(180deg); margin-top: -22px; - ::ng-deep .mat-progress-bar-fill { - background-color: rgb(173, 26, 26); - z-index: 2; - - + mat-progress-bar { + --mdc-linear-progress-active-indicator-color: rgb(173, 26, 26); + --mdc-linear-progress-track-color: rgba(0, 0, 0, 0.01); } - ::ng-deep .mat-progress-bar-fill::after { - background-color: rgb(173, 26, 26); + ::ng-deep .mdc-linear-progress__primary-bar { z-index: 2; - - - } - - - - - ::ng-deep .mat-progress-bar-buffer { - background: rgba(0, 0, 0, 0.01); } } .load-success { - ::ng-deep .mat-progress-bar-fill { - // background-color: rgb(173, 26, 26); - z-index: 2; - - } - ::ng-deep .mat-progress-bar-fill::after { - // background-color: rgb(173, 26, 26); - // z-index: 2; - + mat-progress-bar { + --mdc-linear-progress-track-color: rgba(0, 0, 0, 0.01); } - ::ng-deep .mat-progress-bar-buffer { - background: rgba(0, 0, 0, 0.01); + ::ng-deep .mdc-linear-progress__primary-bar { + z-index: 2; } } - .load-success-old { - ::ng-deep .mat-progress-bar-fill { - // background-color: rgb(173, 26, 26); - z-index: 2; - + mat-progress-bar { + --mdc-linear-progress-track-color: rgba(0, 0, 0, 0.01); } - ::ng-deep .mat-progress-bar-fill::after { - // background-color: rgb(173, 26, 26); - z-index: 2; - - } - ::ng-deep .mat-progress-bar-buffer { - background: rgba(0, 0, 0, 0.01); + ::ng-deep .mdc-linear-progress__primary-bar { + z-index: 2; } } diff --git a/src/app/core/facets-manager/facets-manager.component.scss b/src/app/core/facets-manager/facets-manager.component.scss index f320fe7a2..5c30c7eff 100644 --- a/src/app/core/facets-manager/facets-manager.component.scss +++ b/src/app/core/facets-manager/facets-manager.component.scss @@ -1,163 +1,176 @@ +.mat-expansion-panel-header { + font-family: Roboto, "Helvetica Neue", sans-serif; + font-size: 15px; + height: 48px; + font-weight: 400; +} + +.mat-expansion-panel-content { + font-family: Roboto, "Helvetica Neue", sans-serif; +} + .facet-search-container { - display: flex; - align-items: center; - margin-left: 10px; + display: flex; + align-items: center; + margin-left: 10px; - .mat-form-field { - flex-grow: 1; - } + .mat-form-field { + flex-grow: 1; + } } .strikethrough { - text-decoration: line-through; + text-decoration: line-through; } .facet-search-loading.mat-progress-bar { - margin-top: -18px; - margin-bottom: 1.09em; + margin-top: -18px; + margin-bottom: 1.09em; } .facet-value { + display: flex; + box-sizing: border-box; + width: 100%; + flex-direction: row; + align-items: center; + white-space: nowrap; + padding: 6px 0; + overflow: hidden; + // height: 35px; + + .facet-value-checkbox { + padding: 0 5px 0 0; display: flex; - box-sizing: border-box; - width: 100%; - flex-direction: row; align-items: center; - white-space: nowrap; - padding: 6px 0; - overflow: hidden; - // height: 35px; - - .facet-value-checkbox { - padding: 0 5px 0 0; - display: flex; - align-items: center; - flex: 0 0 auto; - - ::ng-deep .mat-mdc-checkbox .mat-internal-form-field { - width: 24px; - height: 24px; - margin: 0; - padding: 0; - } - - // Keep big click target, but don't let it affect layout - ::ng-deep .mat-mdc-checkbox .mat-mdc-checkbox-touch-target { - position: absolute; - inset: 50% auto auto 50%; - transform: translate(-50%, -50%); - } + flex: 0 0 auto; + ::ng-deep .mat-mdc-checkbox .mat-internal-form-field { + width: 24px; + height: 24px; + margin: 0; + padding: 0; } - .facet-value-label { - padding: 0 5px; - max-width: 150px; - overflow: hidden; - color: var(--label-color); - white-space: normal; + // Keep big click target, but don't let it affect layout + ::ng-deep .mat-mdc-checkbox .mat-mdc-checkbox-touch-target { + position: absolute; + inset: 50% auto auto 50%; + transform: translate(-50%, -50%); } + } + + .facet-value-label { + padding: 0 5px; + max-width: 150px; + overflow: hidden; + color: var(--label-color); + white-space: normal; + } - .facet-value-count { - padding: 0 0 0 3px; - overflow: hidden; - font-weight: 500; + .facet-value-count { + padding: 0 0 0 3px; + overflow: hidden; + font-weight: 500; - .number-fix { - padding-top: 5px; - } + .number-fix { + padding-top: 5px; + } - .button-fix { - max-height: 20px; - max-width: 50px; - } + .button-fix { + max-height: 20px; + max-width: 50px; } + } } .count-fix { - display: flex; + display: flex; } .user-list-button { - padding: 0px 5px; - margin-left: 15px; - color: var(--link-primary-color); + padding: 0px 5px; + margin-left: 15px; + color: var(--link-primary-color); } .include { - ::ng-deep .mdc-checkbox__background { - border-color: var(--include-checkbox-border-color) !important; - } - - ::ng-deep &.mat-mdc-checkbox-checked .mdc-checkbox__background, - ::ng-deep &.mat-mdc-checkbox-indeterminate .mdc-checkbox__background { - background-color: var(--include-checkbox-bg-color) !important; - border-color: var(--include-checkbox-bg-color) !important; - } + ::ng-deep .mdc-checkbox__background { + border-color: var(--include-checkbox-border-color) !important; + } + + ::ng-deep &.mat-mdc-checkbox-checked .mdc-checkbox__background, + ::ng-deep &.mat-mdc-checkbox-indeterminate .mdc-checkbox__background { + background-color: var(--include-checkbox-bg-color) !important; + border-color: var(--include-checkbox-bg-color) !important; + } } .exclude { - margin-left: 2px; - ::ng-deep .mdc-checkbox__background { - border-color: var(--exclude-checkbox-border-color) !important; - } - - ::ng-deep &.mat-mdc-checkbox-checked .mdc-checkbox__background, - ::ng-deep &.mat-mdc-checkbox-indeterminate .mdc-checkbox__background { - background-color: var(--exclude-checkbox-bg-color) !important; - border-color: var(--exclude-checkbox-bg-color) !important; - } + margin-left: 2px; + ::ng-deep .mdc-checkbox__background { + border-color: var(--exclude-checkbox-border-color) !important; + } + + ::ng-deep &.mat-mdc-checkbox-checked .mdc-checkbox__background, + ::ng-deep &.mat-mdc-checkbox-indeterminate .mdc-checkbox__background { + background-color: var(--exclude-checkbox-bg-color) !important; + border-color: var(--exclude-checkbox-bg-color) !important; + } } .show-more { - color: var(--link-primary-color); + color: var(--link-primary-color); } .show-more:hover { - text-decoration: underline; + text-decoration: underline; } .facet-advanced-options-link { - margin-top: 7px; - color: var(--link-primary-color); - margin-left: 10px; + margin-top: 7px; + color: var(--link-primary-color); + margin-left: 10px; } .facet-actions { - display: flex; - align-items: center; - margin-top: 10px; + display: flex; + align-items: center; + margin-top: 10px; - .pull-right { - margin-left: auto; - } + .pull-right { + margin-left: auto; + } - .mat-flat-button { - min-width: 70px; + button[mat-flat-button] { + min-width: 70px; - &:not(:first-child) { - margin-left: 5px; - } + &:not(:first-child) { + margin-left: 5px; } + } } .deprecated { - font-size: 14px; - margin-left: 25px; - color: var(--deprecated-color); - margin-bottom: 15px; - -::ng-deep .mat-checkbox-inner-container { - height: 14px; - width: 14px; - } - - ::ng-deep .mat-checkbox-layout { - margin-bottom: 10px; - } + font-size: 14px; + margin-left: 25px; + color: var(--deprecated-color); + margin-bottom: 15px; + + ::ng-deep .mdc-checkbox__background { + width: 14px; + height: 14px; + } + + ::ng-deep .mat-internal-form-field { + margin-bottom: 10px; + } } -::ng-deep .facet-search-container .mat-mdc-form-field .mat-mdc-form-field-infix { +::ng-deep + .facet-search-container + .mat-mdc-form-field + .mat-mdc-form-field-infix { width: 235px !important; flex: 0 1 230px !important; min-width: 0 !important; diff --git a/src/app/core/home/home.component.html b/src/app/core/home/home.component.html index d5615d1f4..653a2e4ac 100644 --- a/src/app/core/home/home.component.html +++ b/src/app/core/home/home.component.html @@ -188,7 +188,10 @@ - + Register Other @@ -216,7 +219,6 @@ > Application - @@ -251,7 +253,6 @@ In Vitro Pharmacology Screening - @@ -279,7 +280,6 @@ *ngIf="bannerMessage && bannerMessage !== ''" > - {{ homeHeader }}
    - Total substances: {{ total | number : "1.0" : "en-US" }} + Total substances: {{ total | number: "1.0" : "en-US" }}
    @@ -395,7 +395,7 @@

    {{ homeHeader }}

    {{ link.display }}
    - {{ link.total | number : "1.0" : "en-US" }} + {{ link.total | number: "1.0" : "en-US" }}
    @@ -457,7 +457,7 @@

    {{ homeHeader }}

    -
    +
    @@ -406,8 +406,8 @@

    {{ homeHeader }}

    Helpful Resources

    - - + + Access - - - - - + + + + +
    diff --git a/src/app/core/references-manager/references-manager.component.ts b/src/app/core/references-manager/references-manager.component.ts index 31ab0ac87..70bd36807 100644 --- a/src/app/core/references-manager/references-manager.component.ts +++ b/src/app/core/references-manager/references-manager.component.ts @@ -1,67 +1,82 @@ -import { Component, OnInit, Input } from '@angular/core'; -import {SubstanceDetail, SubstanceReference} from '../substance/substance.model'; -import {SubstanceService} from '../substance/substance.service'; -import {DatePipe} from '@angular/common'; -import { take } from 'rxjs/operators'; - +import { Component, OnInit, OnChanges, Input, SimpleChanges } from "@angular/core"; +import { + SubstanceDetail, + SubstanceReference, +} from "../substance/substance.model"; +import { SubstanceService } from "../substance/substance.service"; +import { DatePipe } from "@angular/common"; +import { take } from "rxjs/operators"; + @Component({ - selector: 'app-references-manager', - templateUrl: './references-manager.component.html', - styleUrls: ['./references-manager.component.scss'], - standalone: false -}) -export class ReferencesManagerComponent implements OnInit { - @Input() substance?: SubstanceDetail; - @Input() subUUID?: string; - @Input() references: Array; - subRef: Array; - matchedRef: SubstanceReference[] = []; - showmore = false; - displayedColumns: string[] = ['index', 'citation', 'docType', 'tags', 'files', 'lastEdited', 'access']; - - - constructor(private substanceService: SubstanceService) { } - - ngOnInit() { - if (this.substance) { - this.subRef = this.substance.references; - } else if (this.subUUID) { - const subscription = this.substanceService.getSubstanceDetails(this.subUUID).pipe(take(1)).subscribe(response => { - if (response) { - this.substance = response; - this.subRef = this.substance.references; - } - subscription.unsubscribe(); - }, error => { - subscription.unsubscribe(); - }); - } - if (this.subRef) { - this.compileReferences(); - } - } - - compileReferences() { - if (this.substance.references) { - this.substance.references.forEach(ref => { - const uuid = ref.uuid; - if (this.references.indexOf(uuid) > -1) { - this.matchedRef.push(ref); - } - }); - } - } - - convertTimestamp(time: number) { - const datePipe = new DatePipe('en-US'); - return datePipe.transform(time, 'MMM dd, yyyy'); - } - - getParentIndex(uuid: SubstanceReference) { - return (this.subRef.indexOf(uuid) + 1) ; - } -} - - - - + selector: "app-references-manager", + templateUrl: "./references-manager.component.html", + styleUrls: ["./references-manager.component.scss"], + standalone: false, +}) +export class ReferencesManagerComponent implements OnInit, OnChanges { + @Input() substance?: SubstanceDetail; + @Input() subUUID?: string; + @Input() references: Array; + subRef: Array; + matchedRef: SubstanceReference[] = []; + showmore = false; + displayedColumns: string[] = [ + "index", + "citation", + "docType", + "tags", + "files", + "lastEdited", + "access", + ]; + + constructor(private substanceService: SubstanceService) {} + + ngOnChanges(_changes: SimpleChanges) { + if (this.substance && this.references) { + this.subRef = this.substance.references; + this.compileReferences(); + } + } + + ngOnInit() { + if (this.substance) { + this.subRef = this.substance.references; + this.compileReferences(); + } else if (this.subUUID) { + const subscription = this.substanceService + .getSubstanceDetails(this.subUUID) + .pipe(take(1)) + .subscribe( + (response) => { + if (response) { + this.substance = response; + this.subRef = this.substance.references; + this.compileReferences(); + } + subscription.unsubscribe(); + }, + (error) => { + subscription.unsubscribe(); + }, + ); + } + } + + compileReferences() { + if (this.substance.references && this.references) { + this.matchedRef = this.substance.references.filter((ref) => + this.references.indexOf(ref.uuid) > -1 + ); + } + } + + convertTimestamp(time: number) { + const datePipe = new DatePipe("en-US"); + return datePipe.transform(time, "MMM dd, yyyy"); + } + + getParentIndex(uuid: SubstanceReference) { + return this.subRef.indexOf(uuid) + 1; + } +} diff --git a/src/app/core/substance-details/substance-overview/substance-overview.component.ts b/src/app/core/substance-details/substance-overview/substance-overview.component.ts index ba001a4f6..b526861fb 100644 --- a/src/app/core/substance-details/substance-overview/substance-overview.component.ts +++ b/src/app/core/substance-details/substance-overview/substance-overview.component.ts @@ -81,13 +81,13 @@ export class SubstanceOverviewComponent extends SubstanceCardBase implements OnI if (this.substance?.version) { this.versionControl.setValue(this.substance.version.toString()); } + this.getSubtypeRefs(this.substance); this.canEdit=await this.authService.canEditData(); this.canRestoreVersions = await this.authService.hasSpecificPrivilege("Restore Previous Versions"); this.isEditable =this.canEdit && this.substance.substanceClass != null && (formSections[this.substance.substanceClass.toLowerCase()] != null || formSections[this.substance.substanceClass] != null); - this.getSubtypeRefs(this.substance); const theJSON = JSON.stringify(this.substance); const uri = this.sanitizer.bypassSecurityTrustUrl('data:text/json;charset=UTF-8,' + encodeURIComponent(theJSON)); this.downloadJsonHref = uri; diff --git a/src/app/core/substance-edit-import-dialog/substance-edit-import-dialog.component.html b/src/app/core/substance-edit-import-dialog/substance-edit-import-dialog.component.html index 939619ada..b21c55398 100644 --- a/src/app/core/substance-edit-import-dialog/substance-edit-import-dialog.component.html +++ b/src/app/core/substance-edit-import-dialog/substance-edit-import-dialog.component.html @@ -1,4 +1,4 @@ -

    {{ title }}

    +

    {{ title }}

    diff --git a/src/app/core/substance-form/substance-form.component.scss b/src/app/core/substance-form/substance-form.component.scss index 299ab845a..886cc87cb 100644 --- a/src/app/core/substance-form/substance-form.component.scss +++ b/src/app/core/substance-form/substance-form.component.scss @@ -88,6 +88,11 @@ mat-select-panel { ::ng-deep .mat-form-field-wrapper { padding-bottom: 20px !important; } + + ::ng-deep .mat-mdc-form-field-infix { + padding-top: 24px !important; + padding-bottom: 2px !important; + } } ::ng-deep .mat-form-field-wrapper { @@ -262,11 +267,20 @@ mat-select-panel { .chip { background-color: var(--regular-white-color); - border-radius: 50%; - padding: 3px 5px; + border-radius: 999px; + min-width: 20px; + height: 20px; + padding: 0 5px; + display: inline-flex; + align-items: center; + justify-content: center; margin-left: 5px; color: var(--link-color); - // height: 33px; + font-size: 12px; + font-weight: 600; + line-height: 1; + flex-shrink: 0; + box-sizing: border-box; } .mat-button, @@ -277,5 +291,5 @@ mat-select-panel { } ::ng-deep .mat-mdc-form-field-infix { - padding-top: 18px !important; + padding-top: 24px !important; } diff --git a/src/app/core/substance-text-search/substance-text-search.component.scss b/src/app/core/substance-text-search/substance-text-search.component.scss index ec172c7e4..e3fbf7d5f 100644 --- a/src/app/core/substance-text-search/substance-text-search.component.scss +++ b/src/app/core/substance-text-search/substance-text-search.component.scss @@ -34,8 +34,8 @@ form { // Form field infix padding ::ng-deep .mat-mdc-form-field-infix { - padding-top: 0.5em; - padding-bottom: 8px; + padding-top: 0.5em !important; + padding-bottom: 8px !important; } // Text field wrapper padding @@ -190,7 +190,9 @@ form:not(.header) { @media (max-width: $nav-breaking-point) { .search-container { mat-form-field { - width: 0; + max-width: 0; + overflow: hidden; + transition: max-width 300ms ease; } } @@ -210,16 +212,21 @@ form:not(.header) { padding-left: 16px; background-color: var(--primary-color); overflow: hidden; + z-index: 2; form { + height: 100%; max-height: 100%; } - .mat-form-field { - width: 100%; - flex-flow: 1; - animation-name: expandWidth; - animation-duration: 300ms; + mat-form-field { + flex-grow: 1; + max-width: 2000px; + overflow: visible; + } + + ::ng-deep .mat-mdc-form-field-subscript-wrapper { + display: none; } .search-button { @@ -230,10 +237,6 @@ form:not(.header) { display: none !important; } - // ::ng-deep .mat-form-field-infix { - // border-top: 2px solid transparent; - // } - .close-button { display: inline-block !important; width: auto; @@ -293,9 +296,8 @@ form:not(.header) { .deactivate-search { &.search-container { - .mat-form-field { - animation-name: reduceWidth; - animation-duration: 300ms; + mat-form-field { + max-width: 0; } } } diff --git a/src/app/core/substances-browse/substances-browse.component.scss b/src/app/core/substances-browse/substances-browse.component.scss index 8ff5691ac..afe80d759 100644 --- a/src/app/core/substances-browse/substances-browse.component.scss +++ b/src/app/core/substances-browse/substances-browse.component.scss @@ -43,7 +43,7 @@ .wildcard-div { margin: 10px 30px; - width: 20%; + // width: 20%; display: inline-flex; } @@ -87,6 +87,11 @@ background-color: var(--regular-white-color); font-size: 14px; } + + .reset-facets-button { + align-self: stretch; + --mat-button-protected-container-height: auto; + } } .controls-container { @@ -402,7 +407,10 @@ color: var(--regular-black-color); background-color: white; border-radius: 4px; - box-shadow: 0 3px 1px -2px #0003, 0 2px 2px #00000024, 0 1px 5px #0000001f; + box-shadow: + 0 3px 1px -2px #0003, + 0 2px 2px #00000024, + 0 1px 5px #0000001f; padding: 16px 16px; } @@ -607,7 +615,7 @@ // margin-bottom: -7px; ::ng-deep .mat-mdc-form-field-infix { padding-bottom: 10px; -} + } } .advanced { @@ -633,13 +641,12 @@ flex-shrink: 0; line-height: 40px; border-radius: 50%; - + .mat-icon { margin: 0; } + } } -} - .mat-elevation-z2 { background-color: var(--regular-white-color); diff --git a/src/app/fda/impurities/impurities-form/impurities-test-form/impurities-test-form.component.scss b/src/app/fda/impurities/impurities-form/impurities-test-form/impurities-test-form.component.scss index 274c2901d..c935d6846 100644 --- a/src/app/fda/impurities/impurities-form/impurities-test-form/impurities-test-form.component.scss +++ b/src/app/fda/impurities/impurities-form/impurities-test-form/impurities-test-form.component.scss @@ -7,7 +7,10 @@ background-color: var(--regular-white-color); align-items: center; justify-content: center; - box-shadow: 0px 3px 3px -2px var(--box-shadow-color), 0px 3px 4px 0px var(--box-shadow-color-2), 0px 1px 8px 0px var(--box-shadow-color-3); + box-shadow: + 0px 3px 3px -2px var(--box-shadow-color), + 0px 3px 4px 0px var(--box-shadow-color-2), + 0px 1px 8px 0px var(--box-shadow-color-3); z-index: 1001; } @@ -17,8 +20,8 @@ } .height30px { - height: 30px; - } + height: 30px; +} .scrollable-container { padding-top: 15px; @@ -124,7 +127,7 @@ } .col-4 { - width: calc((100% - 20px) / (6/4)); + width: calc((100% - 20px) / (6 / 4)); margin-right: 20px; } @@ -133,7 +136,7 @@ } .col-5 { - width: calc((100% - 10px) / (6/5)); + width: calc((100% - 10px) / (6 / 5)); margin-right: 10px; } @@ -142,7 +145,7 @@ } .col-5-more { - width: calc((100% + 100px) / (6/5)); + width: calc((100% + 100px) / (6 / 5)); margin-right: 10px; } @@ -256,7 +259,6 @@ .warning-message { color: var(--warning-dialog-color); background-color: var(--warning-dialog-bg-color); - } .error-message { @@ -317,6 +319,7 @@ .marginleft20px { margin-left: 20px; + margin-top: 15px; } .marginleft50px { @@ -526,8 +529,7 @@ width15percent { transform: translateY(-50%); } -.mat-form-field-style> { - +.mat-form-field-style > { /* OVERWRITE MATERIAL INPUT FIELDS */ .mat-form-field-infix { color: var(--regular-blue-color); @@ -553,11 +555,15 @@ width15percent { /*Focused: change color of underline*/ .mat-form-field-ripple { background-color: var(--mat-form-field-focused-color) !important; - ; } .mat-form-field-disabled .mat-form-field-underline { - background-image: linear-gradient(to right, var(--img-linear-gradient-start-color) 0, var(--textarea-dark-border-color) 10%, var(--img-linear-gradient-color) 0) !important; + background-image: linear-gradient( + to right, + var(--img-linear-gradient-start-color) 0, + var(--textarea-dark-border-color) 10%, + var(--img-linear-gradient-color) 0 + ) !important; background-size: 1px 100% !important; background-repeat: repeat-x !important; cursor: not-allowed; @@ -570,7 +576,6 @@ width15percent { mat-hint { color: var(--regular-red-color) !important; } - } .errortext { @@ -616,7 +621,6 @@ width15percent { max-width: 50px; ::ng-deep .mat-form-field { - .mat-form-field-label { font-size: 16px; } @@ -723,7 +727,6 @@ td.mat-cell:last-child { /* Table Style End */ - fieldset.border { border: solid 2px var(--fieldset-red-border-color) !important; padding: 0 10px 10px 10px; @@ -759,4 +762,4 @@ legend.border2 { font-family: Verdana; font-weight: bold; margin-bottom: 0px; -} \ No newline at end of file +} diff --git a/src/app/fda/invitro-pharmacology/invitro-pharmacology-form/invitro-pharmacology-assay-form/invitro-pharmacology-assay-form.component.html b/src/app/fda/invitro-pharmacology/invitro-pharmacology-form/invitro-pharmacology-assay-form/invitro-pharmacology-assay-form.component.html index 7ccb5d869..4aaec6e5a 100644 --- a/src/app/fda/invitro-pharmacology/invitro-pharmacology-form/invitro-pharmacology-assay-form/invitro-pharmacology-assay-form.component.html +++ b/src/app/fda/invitro-pharmacology/invitro-pharmacology-form/invitro-pharmacology-assay-form/invitro-pharmacology-assay-form.component.html @@ -1,60 +1,120 @@
    - -     - - - Export JSON -     +     + + + Export JSON     - + - - + View Assay Details - -        - +          - +       - -
    - + +
    + -
    +
    - {{submissionMessage}} + {{ submissionMessage }}
    -
    - -
    + +
    Please correct or dismiss the following errors and submit again:
    -
    -
    - {{message.messageType}}
    -
    {{message.message}}
    {{link.text}}
    -
    @@ -62,78 +122,114 @@
    - +
    - -
    -
    - +
    + + +
    -
    - {{message}} + {{ message }}
    -
    - {{title}} + {{ title }}    - - -     + +    
    -
    - Created By: {{assay.createdBy}}    - Create Date:{{assay.createdDate|date: 'MM/dd/yyyy hh:mm:ss - a'}}
    - Modified By: {{assay.modifiedBy}}    - Modify Date: {{assay.modifiedDate|date: 'MM/dd/yyyy hh:mm:ss a'}} +
    + Created By: {{ assay.createdBy }}    + Create Date:{{ + assay.createdDate + | date + : "MM/dd/yyyy hh:mm:ss + a" + }}
    + Modified By: + {{ assay.modifiedBy }}    + Modify Date: + {{ assay.modifiedDate | date: "MM/dd/yyyy hh:mm:ss a" }}
    -
    - -
    - +
    + +
    +
    - - -
    - Assay Set: * + Assay Set: *
    -
    - - - {{data.value}} + + + {{ data.value }}
    @@ -141,18 +237,28 @@
    - + Enter New Assay Set + -
    +
    - +
    - -
    - -
    +
    + +
    +
    @@ -160,26 +266,44 @@
    - {{assay.assayId}} + {{ assay.assayId }}
    - External Assay Source: * + External Assay Source: *
    - +
    - External Assay ID: * + External Assay ID: *
    - +
    @@ -188,8 +312,12 @@ External Assay Reference URL:
    - + @@ -198,7 +326,12 @@ Assay Title: - + @@ -206,8 +339,14 @@
    Assay Format:
    - + @@ -215,8 +354,14 @@
    Assay Mode:
    - + @@ -224,8 +369,14 @@
    Bioassay Type:
    - + @@ -233,8 +384,14 @@
    Bioassay Class:
    - + @@ -242,8 +399,14 @@
    Study Type:
    - + @@ -251,8 +414,14 @@
    Detection Method:
    - + @@ -260,8 +429,14 @@
    Presentation Type:
    - + @@ -270,7 +445,12 @@ Presentation: - + @@ -278,8 +458,14 @@
    Public Domain:
    - + @@ -288,32 +474,51 @@ Target Species: - +
    - Target Name: * + Target Name: *
    - -
    + + + +
    @@ -322,21 +527,35 @@ - -
    +
    + + +
    @@ -345,29 +564,47 @@ - -
    +
    + + +
    Standard Ligand/Sub Concentration:
    - +
    @@ -375,32 +612,46 @@
    Standard Ligand/Sub Concent Units:
    - + (valueChange)=" + assay.standardLigandSubstrateConcentrationUnits = $event + " + > -
    -
    - Analytes - ({{assay.invitroAssayAnalytes.length}}) -     + Analytes ({{ assay.invitroAssayAnalytes.length }})    
    -
    -
    - +
    Analyte: @@ -408,45 +659,67 @@
    - + (searchValueOut)=" + searchValueOutChange($event, ANALYTE, indexAnalyte) + " + (searchPerformed)=" + nameSearch($event, ANALYTE, indexAnalyte) + " + eventCategory="selectorSearch" + > - +
    + +
    + +
    + +
    + -
    - -
    - - - - -


    +


    - - - - -


    + + + + +


    - + + -



    -
    There is no Assay Screening Data found.
    -
    \ No newline at end of file +



    +
    + There is no Assay Screening Data found. +
    + diff --git a/src/app/fda/invitro-pharmacology/invitro-pharmacology-form/invitro-pharmacology-assay-form/invitro-pharmacology-assay-form.component.scss b/src/app/fda/invitro-pharmacology/invitro-pharmacology-form/invitro-pharmacology-assay-form/invitro-pharmacology-assay-form.component.scss index a2b962d66..cd6d2a4bd 100644 --- a/src/app/fda/invitro-pharmacology/invitro-pharmacology-form/invitro-pharmacology-assay-form/invitro-pharmacology-assay-form.component.scss +++ b/src/app/fda/invitro-pharmacology/invitro-pharmacology-form/invitro-pharmacology-assay-form/invitro-pharmacology-assay-form.component.scss @@ -552,7 +552,7 @@ hr { height: 5px; } -.mat-card { +.mat-mdc-card { max-width: 1140px; } diff --git a/src/app/fda/invitro-pharmacology/invitro-pharmacology-form/invitro-pharmacology-assayset-form/invitro-pharmacology-assayset-form.component.html b/src/app/fda/invitro-pharmacology/invitro-pharmacology-form/invitro-pharmacology-assayset-form/invitro-pharmacology-assayset-form.component.html index 4f6e7120f..8a130f862 100644 --- a/src/app/fda/invitro-pharmacology/invitro-pharmacology-form/invitro-pharmacology-assayset-form/invitro-pharmacology-assayset-form.component.html +++ b/src/app/fda/invitro-pharmacology/invitro-pharmacology-form/invitro-pharmacology-assayset-form/invitro-pharmacology-assayset-form.component.html @@ -180,7 +180,7 @@
    - Enter Existing Assay Se + Enter Existing Assay Set Date: Mon, 30 Mar 2026 15:43:10 -0400 Subject: [PATCH 342/408] updated application IVP --- .../application-form.component.ts | 10 +- ...ology-screening-data-import.component.html | 41 +- ...ology-screening-data-import.component.scss | 4 + ...acology-screening-data-import.component.ts | 354 ++++++++++++++---- 4 files changed, 321 insertions(+), 88 deletions(-) diff --git a/src/app/fda/application/application-form/application-form.component.ts b/src/app/fda/application/application-form/application-form.component.ts index 61c4ca644..0aab8a2b6 100644 --- a/src/app/fda/application/application-form/application-form.component.ts +++ b/src/app/fda/application/application-form/application-form.component.ts @@ -253,6 +253,12 @@ export class ApplicationFormComponent implements OnInit, AfterViewInit, OnDestro // Validate Ingredient Average, Low, High, LowLimit, HighLimit should be integer/number elementProd.applicationIngredientList.forEach(elementIngred => { if (elementIngred != null) { + + // Trim Applicant Ingredient Name + if (elementIngred.applicantIngredName) { + elementIngred.applicantIngredName = elementIngred.applicantIngredName.trim(); + } + if (elementIngred.average) { if (this.isNumber(elementIngred.average) === false) { this.setValidationMessage('Average must be a number'); @@ -522,7 +528,7 @@ export class ApplicationFormComponent implements OnInit, AfterViewInit, OnDestro } else { const isValid = this.validateSubmitDateWithStatusDate(this.application.submitDate, this.application.statusDate); if (isValid === false) { - this.submitDateMessage = 'Submit Date should be earlier than Status Date;'; + this.submitDateMessage = 'Submit Date should be earlier than Status Date'; } } } @@ -539,7 +545,7 @@ export class ApplicationFormComponent implements OnInit, AfterViewInit, OnDestro } else { isValid = this.validateSubmitDateWithStatusDate(this.application.submitDate, this.application.statusDate); if (isValid === false) { - this.submitDateMessage = 'Submit Date should be earlier than Status Date;'; + this.submitDateMessage = 'Submit Date should be earlier than Status Date'; } } } diff --git a/src/app/fda/invitro-pharmacology/invitro-pharmacology-screening-data-import/invitro-pharmacology-screening-data-import.component.html b/src/app/fda/invitro-pharmacology/invitro-pharmacology-screening-data-import/invitro-pharmacology-screening-data-import.component.html index efe0660f4..461ff7ada 100644 --- a/src/app/fda/invitro-pharmacology/invitro-pharmacology-screening-data-import/invitro-pharmacology-screening-data-import.component.html +++ b/src/app/fda/invitro-pharmacology/invitro-pharmacology-screening-data-import/invitro-pharmacology-screening-data-import.component.html @@ -9,6 +9,11 @@
    +
    + +
    +
    - - + + +

    Importing Assays

    + +
    + +

    + Saved {{ savedCount }} of {{ totalAssays }} Assays +

    + + +

    {{ progressMessage }}

    + + +
    +

    An error occurred:

    +

    {{ errorMessage }}

    +
    +
    + +
    + + +
    +
    +
    @@ -420,7 +447,7 @@ {{result.assaySet}} - +
    {{result.externalAssaySource}} diff --git a/src/app/fda/invitro-pharmacology/invitro-pharmacology-screening-data-import/invitro-pharmacology-screening-data-import.component.scss b/src/app/fda/invitro-pharmacology/invitro-pharmacology-screening-data-import/invitro-pharmacology-screening-data-import.component.scss index 00b92c70a..30ea867e6 100644 --- a/src/app/fda/invitro-pharmacology/invitro-pharmacology-screening-data-import/invitro-pharmacology-screening-data-import.component.scss +++ b/src/app/fda/invitro-pharmacology/invitro-pharmacology-screening-data-import/invitro-pharmacology-screening-data-import.component.scss @@ -338,6 +338,10 @@ margin-left: 50px; } +.marginright30px { + margin-right: 30px; +} + .padtop5px { padding-top: 5px; } diff --git a/src/app/fda/invitro-pharmacology/invitro-pharmacology-screening-data-import/invitro-pharmacology-screening-data-import.component.ts b/src/app/fda/invitro-pharmacology/invitro-pharmacology-screening-data-import/invitro-pharmacology-screening-data-import.component.ts index 40614e01e..dd34c33a9 100644 --- a/src/app/fda/invitro-pharmacology/invitro-pharmacology-screening-data-import/invitro-pharmacology-screening-data-import.component.ts +++ b/src/app/fda/invitro-pharmacology/invitro-pharmacology-screening-data-import/invitro-pharmacology-screening-data-import.component.ts @@ -1,4 +1,4 @@ -import { Component, OnInit, OnDestroy } from '@angular/core'; +import { Component, ViewChild, TemplateRef, OnInit, OnDestroy } from '@angular/core'; import { ActivatedRoute, Router } from '@angular/router'; import { DomSanitizer, SafeUrl } from '@angular/platform-browser'; import { FormBuilder } from '@angular/forms'; @@ -7,11 +7,13 @@ import { Title } from '@angular/platform-browser'; import { DatePipe, formatDate } from '@angular/common'; import { OverlayContainer } from '@angular/cdk/overlay'; import { Subscription } from 'rxjs'; -import { take, map } from 'rxjs/operators'; +import { take, map, finalize } from 'rxjs/operators'; +import { forkJoin, from, tap, of, toArray, concatMap, catchError, throwError } from 'rxjs'; import * as moment from 'moment'; import * as _ from 'lodash'; import * as XLSX from 'xlsx'; + /* GSRS Core Imports */ import { AuthService } from '@gsrs-core/auth/auth.service'; import { UtilsService } from '../../../core/utils/utils.service'; @@ -34,13 +36,14 @@ import { } from '../model/invitro-pharmacology.model'; @Component({ - selector: 'app-invitro-pharmacology-screening-data-import', - templateUrl: './invitro-pharmacology-screening-data-import.component.html', - styleUrls: ['./invitro-pharmacology-screening-data-import.component.scss'], - standalone: false + selector: 'app-invitro-pharmacology-screening-data-import', + templateUrl: './invitro-pharmacology-screening-data-import.component.html', + styleUrls: ['./invitro-pharmacology-screening-data-import.component.scss'], + standalone: false }) export class InvitroPharmacologyScreeningDataImportComponent implements OnInit { + @ViewChild('progressDialogTemplate') progressDialogTemplateRef: TemplateRef; private TEST_AGENT = "Test Agent"; @@ -68,14 +71,25 @@ export class InvitroPharmacologyScreeningDataImportComponent implements OnInit { importDataList: Array = []; importedBulkAssayJson: Array = [{}]; importedAssayJson: any; + currentDialogData: any; message = ''; submitMessage = ''; resultMessage = ''; - disableImportButton = "true"; + disableValidateButton = "true"; + disableImportButton = "false"; + isExcelDataLoaded = false; canUpdate: boolean = false; + /* Save Progress Bar variables */ + progressMessage: string = 'Initializing...'; + savedCount: number = 0; + totalAssays: number = 0; + isComplete: boolean = false; + isError: boolean = false; + errorMessage: string = ''; + constructor( private activatedRoute: ActivatedRoute, private router: Router, @@ -97,7 +111,7 @@ export class InvitroPharmacologyScreeningDataImportComponent implements OnInit { this.titleService.setTitle("IVP Import Screening Data"); this.canUpdate = await this.authService.hasSpecificPrivilege('Edit'); - + } ngAfterViewInit() { @@ -119,7 +133,7 @@ export class InvitroPharmacologyScreeningDataImportComponent implements OnInit { // Empty the list this.importDataList.length = 0; //requiredFieldMissingArray = [{}]; - + this.initializeRequiredFieldArray(); // Assign FileReader @@ -155,7 +169,7 @@ export class InvitroPharmacologyScreeningDataImportComponent implements OnInit { this.getInvitroResults(workbook); // Validate all data from Excel file - this.validate(); + // this.validate(); } // reader.onload @@ -238,10 +252,6 @@ export class InvitroPharmacologyScreeningDataImportComponent implements OnInit { this.invtroReferences.push(this.invitroReference); } - // Set Reference to InvitroAssayResultInformation - // this.invitroResultInfo.invitroReferences[0].primaryReference = true; - // this.invitroResultInfo.invitroReferences[0] = this.invitroReference; - } getInvitroLaboratory(workbook: XLSX.WorkBook) { @@ -415,7 +425,6 @@ export class InvitroPharmacologyScreeningDataImportComponent implements OnInit { } // for loop Column } // for loop Row - //this.invitroSponsorReport.invitroSponsorSubmitters.push(this.invitroSponsorSubmitter); } getInvitroSponsorReport(workbook: XLSX.WorkBook) { @@ -707,6 +716,13 @@ export class InvitroPharmacologyScreeningDataImportComponent implements OnInit { } }); + + // Only enable validate button if there are records in the Excel file + if (this.invitroResultsTemp && this.invitroResultsTemp.length > 0) { + this.disableValidateButton = 'false'; + } else { + this.submitMessage = "Excel file does not contain any data. Please add data and try again."; + } } getValue(object: any): string { @@ -802,6 +818,90 @@ export class InvitroPharmacologyScreeningDataImportComponent implements OnInit { */ checkResultAssayFoundInDatabase() { + this.resultMessage = 'Checking Assays in the database...'; + this.disableImportButton = 'true'; + + // This object will now carry the source, ID, and the cached assay. + const initialState = { + lastCheckedSource: '', + lastCheckedAssayId: '', // ADDED: To store the last checked ID + cachedAssay: null as InvitroAssayInformation | null + }; + + from(this.invitroResultsTemp).pipe( + // concatMap ensures each result is processed one by one, in order. + concatMap((result, index) => { + // If the result is invalid, skip it. + if (!result.externalAssaySource || !result.externalAssayId) { + result.assayFoundInDb = 'false'; + return of({ result, status: 'skipped' }); + } + + // UPDATED: Check if both source AND ID match the last processed item. + if ( + initialState.lastCheckedSource === result.externalAssaySource && + initialState.lastCheckedAssayId === result.externalAssayId && + initialState.cachedAssay + ) { + // If YES, reuse the cached assay. No database call is needed. + console.log(`Reusing cached assay for source/ID: ${result.externalAssaySource}/${result.externalAssayId}`); + result.assayFoundInDb = 'true'; + this.createNewScreeningData(_.cloneDeep(initialState.cachedAssay), result); + return of({ result, status: 'reused' }); + } else { + // If NO, we must make a new database call. + console.log(`Fetching new assay for source/ID: ${result.externalAssaySource}/${result.externalAssayId}`); + + // Update the state for the next iteration with the current source and ID. + initialState.lastCheckedSource = result.externalAssaySource; + initialState.lastCheckedAssayId = result.externalAssayId; // ADDED: Update the ID in our state + + return this.invitroPharmacologyService.getAssayByExternalAssay( + result.externalAssaySource, + result.externalAssayId + ).pipe( + map(dbAssay => { + if (dbAssay) { + // Assay found: update the result and cache it. + result.assayFoundInDb = 'true'; + initialState.cachedAssay = _.cloneDeep(dbAssay); // Cache the new assay + this.createNewScreeningData(dbAssay, result); + } else { + // Assay not found: update result and clear the cache. + result.assayFoundInDb = 'false'; + initialState.cachedAssay = null; // Invalidate cache + } + return { result, status: 'fetched' }; + }), + catchError(error => { + console.log("Import Screening data - error getting Assay", error); + result.assayFoundInDb = 'Error getting Assay'; + initialState.cachedAssay = null; // Invalidate cache on error + return of({ result, status: 'error' }); + }) + ); + } + }), + // Collect all processed results into a single array. + toArray() + ).subscribe(processedResults => { + // This block executes ONCE after all items have been processed sequentially. + const allAssaysFound = this.invitroResultsTemp.every(r => r.assayFoundInDb === 'true'); + + this.resultMessage = allAssaysFound + ? 'All assays verified.' + : 'Some assays were not found or could not be verified. Please review.'; + + this.disableImportButton = 'false'; + console.log('Sequential check complete.'); + }); + } + + /* + checkResultAssayFoundInDatabase_2() { + let localAssay: InvitroAssayInformation; + let checkedExternalSource = ''; + let checkedExternalAssayId = ''; let foundallAssays = 'true'; this.resultMessage = ''; @@ -811,47 +911,43 @@ export class InvitroPharmacologyScreeningDataImportComponent implements OnInit { if (result.externalAssaySource && result.externalAssayId) { this.resultMessage = 'Checking Assays in the database ...'; + + if (checkedExternalSource !== result.externalAssaySource) { + checkedExternalSource = result.externalAssaySource; - /* - // CONTROL ASSAY CHECK, check if Result Assays match control Assays - this.invitroControlsTemp.forEach(ctrl => { - if (ctrl) { - if ((ctrl.externalAssaySource) && (ctrl.externalAssayId)) { + const invitroSubscribe = this.invitroPharmacologyService.getAssayByExternalAssay(result.externalAssaySource, result.externalAssayId).subscribe(assay => { + if (assay) { - // if Result and Control Assays match - if ((ctrl.externalAssaySource.externalAssaySource === result.externalAssaySource) - && (ctrl.externalAssayId === result.externalAssayId)) { - controlAssayMatch = true; - } - } - } // if control object exists - }); // control loop - */ + localAssay = _.cloneDeep(assay); + // Assay Found in the Database + result.assayFoundInDb = 'true'; - const invitroSubscribe = this.invitroPharmacologyService.getAssayByExternalAssay(result.externalAssaySource, result.externalAssayId).subscribe(assay => { - if (assay) { - // Assay Found in the Database - result.assayFoundInDb = 'true'; + this.createNewScreeningData(assay, result); + } + else { + // Assay NOT Found in the Database + result.assayFoundInDb = 'false'; + foundallAssays = 'false'; + } - this.createNewScreeningData(assay, result); - } - else { - // Assay NOT Found in the Database - result.assayFoundInDb = 'false'; - foundallAssays = 'false'; - } + if (this.invitroResultsTemp.length === (index + 1)) { + this.resultMessage = ''; + } - if (this.invitroResultsTemp.length === (index + 1)) { - this.resultMessage = ''; + }, error => { + result.assayFoundInDb = 'Error getting Assay'; + console.log("Import Screeing data - error getting Assay"); } + ); // subscribe - }, error => { - result.assayFoundInDb = 'Error getting Assay'; - console.log("Import Screeing data - error getting Assay"); - } - ); // subscribe + this.subscriptions.push(invitroSubscribe); + } // checkedExternalSource !== result.externalAssaySource + else { + // Assay Found in the Database + result.assayFoundInDb = 'true'; - this.subscriptions.push(invitroSubscribe); + this.createNewScreeningData(localAssay, result); + } // } else { foundallAssays = 'false'; } @@ -861,6 +957,7 @@ export class InvitroPharmacologyScreeningDataImportComponent implements OnInit { // Enable "Import into the Database" button this.disableImportButton = 'false'; } + */ createNewScreeningData(assay: InvitroAssayInformation, resultElement: any) { // Create new screening object @@ -1037,8 +1134,8 @@ export class InvitroPharmacologyScreeningDataImportComponent implements OnInit { found = true; this.invitroTestAgent.testAgentSubstanceUuid = substance.uuid; - - // let substanceKey = this.generalService.getSubstanceKeyBySubstanceResolver(substance, this.substanceKeyTypeForInvitroPharmacologyConfig); + + // let substanceKey = this.generalService.getSubstanceKeyBySubstanceResolver(substance, this.substanceKeyTypeForInvitroPharmacologyConfig); /* if (fieldName == this.TARGET_NAME) { @@ -1090,94 +1187,192 @@ export class InvitroPharmacologyScreeningDataImportComponent implements OnInit { // Enable Database Import button //if ( this.targetNameCheckCompleted && this.targetNameCheckCompleted && this.targetNameCheckCompleted) { // this.disableImportButton = "false"; - // } + // } } // if content > 0 else { - // this.setValidationMessage(fieldName + ' "' + ingredientName + '" does not exist in the database. Please register this substance first and then import again', validationMessages, index); + // this.setValidationMessage(fieldName + ' "' + ingredientName + '" does not exist in the database. Please register this substance first and then import again', validationMessages, index); } } // if response else { - // this.setValidationMessage(fieldName + ' "' + ingredientName + '" does not exist in the database. Please register this substance first and then import again', validationMessages, index); + // this.setValidationMessage(fieldName + ' "' + ingredientName + '" does not exist in the database. Please register this substance first and then import again', validationMessages, index); } }, error => { - // this.setValidationMessage(fieldName + ' "' + ingredientName + '" does not exist in the database. Please register this substance first and then import again', validationMessages, index); + // this.setValidationMessage(fieldName + ' "' + ingredientName + '" does not exist in the database. Please register this substance first and then import again', validationMessages, index); }, () => { - + }); this.subscriptions.push(substanceSubscribe); } importAssayJSONIntoDatabase() { + if (!this.assayToSave || this.assayToSave.length === 0) { + console.log('No assays to save.'); + return; + } - this.loadingService.setLoading(true); + // This will hold the result information from the first saved assay for final navigation. + let savedResultInfo: any = null; + + // --- A. Initialize state and open the dialog immediately --- + this.savedCount = 0; + this.totalAssays = this.assayToSave.length; + this.progressMessage = 'Preparing to save assays...'; + this.isComplete = false; + this.isError = false; + const dialogRef = this.dialog.open(this.progressDialogTemplateRef, { + width: '500px', + disableClose: true // Prevent user from closing it while the process is running. + }); + + // --- B. Start the sequential save stream using RxJS --- + from(this.assayToSave).pipe( + // 'concatMap' ensures each assay is processed one by one, waiting for the previous save to complete. + concatMap((assay, index) => { + // Update the dialog message before each save attempt. + this.progressMessage = `Saving assay ${index + 1} of ${this.totalAssays}...`; + + const isFirstAssay = (index === 0); + const assayToSave = _.cloneDeep(assay); + const lastScreeningIndex = assayToSave.invitroAssayScreenings.length - 1; + + // Prepare the payload based on whether it's the first assay or a subsequent one. + if (isFirstAssay) { + // For the first assay, attach the full result information object. + this.invitroResultInfo.invitroReferences = this.invtroReferences; + this.invitroResultInfo.invitroLaboratory = this.invitroLaboratory; + this.invitroResultInfo.invitroSponsor = this.invitroSponsor; + this.invitroResultInfo.invitroSponsorReport = this.invitroSponsorReport; + this.invitroResultInfo.invitroTestAgent = this.invitroTestAgent; + assayToSave.invitroAssayScreenings[lastScreeningIndex].invitroAssayResultInformation = this.invitroResultInfo; + } else { + // For all subsequent assays, link them to the first one's result info ID. + if (!savedResultInfo?.id) { + return throwError(() => new Error('Cannot save subsequent assay: Primary result information ID is missing.')); + } + // assayToSave.invitroAssayScreenings[lastScreeningIndex].invitroAssayResultInformation = { id: savedResultInfo.id }; + assayToSave.invitroAssayScreenings[lastScreeningIndex].invitroAssayResultInformation = savedResultInfo; + } + + // Set the assay on the service (following your existing stateful pattern). + this.invitroPharmacologyService.assay = assayToSave; + + // Return the save observable. concatMap will subscribe and wait for it to complete. + return this.invitroPharmacologyService.saveAssay().pipe( + // 'tap' is used for side-effects, like updating the UI, without altering the stream. + tap(savedAssay => { + this.savedCount = index + 1; // Update the count for the dialog. + + // If this was the first assay, capture its result info for the final navigation. + if (isFirstAssay) { + const screening = savedAssay.invitroAssayScreenings[savedAssay.invitroAssayScreenings.length - 1]; + savedResultInfo = screening.invitroAssayResultInformation; + } + }) + ); + }) + ).subscribe({ + // 'next' is handled by tap, so this can be empty. It's called after each successful save. + next: () => { }, + + // --- C. Handle any error in the stream --- + error: err => { + this.isError = true; + this.errorMessage = err.message || 'An unknown error occurred during the save process.'; + this.progressMessage = 'The import process failed.'; + // The 'Close' button on the dialog will now be enabled due to the [disabled] binding. + }, + + // --- D. Handle successful completion of the entire stream --- + complete: () => { + this.isComplete = true; + this.progressMessage = 'All assays have been imported successfully!'; + + // Wait for the user to close the completed dialog before navigating. + dialogRef.afterClosed().subscribe(() => { + console.log('Navigating to edit page...'); + this.invitroPharmacologyService.bypassUpdateCheck(); + this.router.routeReuseStrategy.shouldReuseRoute = () => false; + this.router.onSameUrlNavigation = 'reload'; + this.router.navigate(['/invitro-pharm/', savedResultInfo.id, 'edit']); + }); + } + }); + } + + + /* + importAssayJSONIntoDatabase_2ORIG() { + + this.loadingService.setLoading(true); + let savedResultInfo: any; if (this.assayToSave.length > 0) { - + let firstAssayToSave = this.assayToSave[0]; - + // Set Reference to Result Information Object this.invitroResultInfo.invitroReferences = this.invtroReferences; this.invitroResultInfo.invitroLaboratory = this.invitroLaboratory; this.invitroResultInfo.invitroSponsor = this.invitroSponsor; this.invitroResultInfo.invitroSponsorReport = this.invitroSponsorReport; this.invitroResultInfo.invitroTestAgent = this.invitroTestAgent; - + // Set invitroAssayResultInformation in first Assay Record firstAssayToSave.invitroAssayScreenings[firstAssayToSave.invitroAssayScreenings.length - 1].invitroAssayResultInformation = this.invitroResultInfo; - + // Assign assay to Servive assay this.invitroPharmacologyService.assay = firstAssayToSave; - + const saveOneAssaySubscribe = this.invitroPharmacologyService.saveAssay().subscribe(responseAssay => { if (responseAssay) { if (responseAssay.id) { if (responseAssay.invitroAssayScreenings.length > 0) { - + // Get the last screening from the returned/saved Assay let screening = responseAssay.invitroAssayScreenings[responseAssay.invitroAssayScreenings.length - 1]; - + savedResultInfo = screening.invitroAssayResultInformation; - + // First invitroAssayResultInformation has been saved. Get the id if (savedResultInfo) { - + // Remove/delete the first Assay from the list this.assayToSave.splice(0, 1); - + // CLone/Copy the remaining assay let remainingBulkAssay = _.cloneDeep(this.assayToSave); - + remainingBulkAssay.forEach(assay => { if (assay) { assay.invitroAssayScreenings.forEach(screening => { // Assign the first invitroAssayResultInformation here. - + // screening.invitroAssayResultInformation = savedResultInfo; - + // screening.invitroAssayResultInformation = {}; // screening.invitroAssayResultInformation.id = savedResultInfo.id; // screening.invitroAssayResultInformation.internalVersion = savedResultInfo.internalVersion; - + }); - + assay.invitroAssayScreenings[assay.invitroAssayScreenings.length - 1].invitroAssayResultInformation = savedResultInfo; //assay.invitroAssayScreenings[assay.invitroAssayScreenings.length - 1].invitroAssayResultInformation = {}; //assay.invitroAssayScreenings[assay.invitroAssayScreenings.length - 1].invitroAssayResultInformation.id = savedResultInfo.id; - + // Assign the assay to service assay this.invitroPharmacologyService.assay = assay; - + const saveSubscribe = this.invitroPharmacologyService.saveAssay().subscribe(response => { if (response) { - + setTimeout(() => { // // this.showSubmissionMessages = false; // this.submissionMessage = ''; if (response.id) { this.loadingService.setLoading(false); - + this.invitroPharmacologyService.bypassUpdateCheck(); const id = response.id; this.router.routeReuseStrategy.shouldReuseRoute = () => false; @@ -1186,15 +1381,15 @@ export class InvitroPharmacologyScreeningDataImportComponent implements OnInit { } }, 4000); } // if response remaining assays - + }); this.subscriptions.push(saveSubscribe); - + } // if assay exists in remainingBulk list - + }); // forloop remainingBulk } // if savedResultInfo exists - + } // if responseAssay.invitroAssayScreenings.length > 0 } } @@ -1202,6 +1397,7 @@ export class InvitroPharmacologyScreeningDataImportComponent implements OnInit { this.subscriptions.push(saveOneAssaySubscribe); } } + */ showJSON(): void { const date = new Date(); From 631aa164d02d64e421e44966b916666ff02a26a9 Mon Sep 17 00:00:00 2001 From: Mitch Miller Date: Tue, 31 Mar 2026 15:33:08 -0400 Subject: [PATCH 343/408] use radio buttons for user roles rather than checkboxes. --- .../user-edit-dialog.component.html | 10 +-- .../user-edit-dialog.component.scss | 4 + .../user-edit-dialog.component.ts | 73 ++++++++++++++----- src/app/core/config/config.model.ts | 8 +- src/app/fda/config/config.json | 10 ++- 5 files changed, 78 insertions(+), 27 deletions(-) diff --git a/src/app/core/admin/user-management/user-edit-dialog/user-edit-dialog.component.html b/src/app/core/admin/user-management/user-edit-dialog/user-edit-dialog.component.html index 83e8ac43a..e32a9d7d4 100644 --- a/src/app/core/admin/user-management/user-edit-dialog/user-edit-dialog.component.html +++ b/src/app/core/admin/user-management/user-edit-dialog/user-edit-dialog.component.html @@ -124,12 +124,12 @@

    {{ newUser ? 'Add User' : 'Edit User' }}

    - Roles -
    - + + Roles + {{ role.roleName }} - -
    + +
    diff --git a/src/app/core/admin/user-management/user-edit-dialog/user-edit-dialog.component.scss b/src/app/core/admin/user-management/user-edit-dialog/user-edit-dialog.component.scss index ab740cee2..2e7e0d53d 100644 --- a/src/app/core/admin/user-management/user-edit-dialog/user-edit-dialog.component.scss +++ b/src/app/core/admin/user-management/user-edit-dialog/user-edit-dialog.component.scss @@ -90,3 +90,7 @@ font-size: 20px; } +.radio-item { + display: block !important; // Override Material inline flex + margin-bottom: 8px; +} \ No newline at end of file diff --git a/src/app/core/admin/user-management/user-edit-dialog/user-edit-dialog.component.ts b/src/app/core/admin/user-management/user-edit-dialog/user-edit-dialog.component.ts index 9105d1d44..2a887c868 100644 --- a/src/app/core/admin/user-management/user-edit-dialog/user-edit-dialog.component.ts +++ b/src/app/core/admin/user-management/user-edit-dialog/user-edit-dialog.component.ts @@ -7,6 +7,7 @@ import { AuthService, Auth } from '@gsrs-core/auth'; import { take } from 'rxjs/operators'; import { AssignableRole, UserEditObject } from '@gsrs-core/admin/admin-objects.model'; import { Router } from '@angular/router'; +import { ConfigService } from "../../../config/config.service"; @Component({ selector: 'app-user-edit-dialog', @@ -33,6 +34,7 @@ export class UserEditDialogComponent implements OnInit { isError: boolean = false; availableRoleNames: string[]; assignableRoles: AssignableRole[]; + selectedRole: string; roles = [ {name: 'Query', hasRole: false}, @@ -47,7 +49,8 @@ export class UserEditDialogComponent implements OnInit { public dialogRef: MatDialogRef, private authService: AuthService, private router: Router, - @Inject(MAT_DIALOG_DATA) public data: any + @Inject(MAT_DIALOG_DATA) public data: any, + private configService: ConfigService, ) { this.user = data.user; this.userID = data.userID; @@ -164,12 +167,7 @@ export class UserEditDialogComponent implements OnInit { this.message = 'Cancel or submit new password to save other changes'; } else { this.isError = false; - const rolesArr = []; - this.assignableRoles.forEach(role => { - if (role.assigned) { - rolesArr.push(role.roleName); - } - }); + const rolesArr = [this.selectedRole]; const groups = []; this.groups.forEach(group => { if (group.hasGroup ) { @@ -227,12 +225,7 @@ export class UserEditDialogComponent implements OnInit { addUser(): void { this.isError = false; if (this.newPassword === this.newPasswordConfirm) { - const rolesArr = []; - this.assignableRoles.forEach(role => { - if (role.assigned) { - rolesArr.push(role.roleName); - } - }); + const rolesArr = [this.selectedRole]; const groups = []; this.groups.forEach(group => { if (group.hasGroup ) { @@ -353,16 +346,56 @@ export class UserEditDialogComponent implements OnInit { return false; } + private getRoleNumericValue(roleName: string): number { + if(!roleName || roleName === null || roleName.length ==- 0 ) return + (this.configService.configData.roleSortingConfig && this.configService.configData.roleSortingConfig["null"] != null ) + ? this.configService.configData.roleSortingConfig["null"] : 0; + + if(roleName.toUpperCase() in this.configService.configData.roleSortingConfig) { + return this.configService.configData.roleSortingConfig[roleName.toUpperCase()]; + } + return 1; + } + private setupAssignableRoles() { this.assignableRoles = []; this.adminService.getAllAvailableRoles().subscribe(roleNames => { - this.availableRoleNames = roleNames; - this.availableRoleNames.forEach(r=>{ - let hasRole:boolean = this.userHasRole(r); - let newRole = {roleName: r, assigned: hasRole }; - this.assignableRoles.push(newRole); - }) - }); + this.availableRoleNames = roleNames; + this.availableRoleNames.forEach(r=>{ + let hasRole:boolean = this.userHasRole(r); + let newRole = {roleName: r, assigned: hasRole }; + this.assignableRoles.push(newRole); + }); + this.assignableRoles.sort( (role1: AssignableRole, role2: AssignableRole )=> { + return this.getRoleNumericValue(role2.roleName) - this.getRoleNumericValue(role1.roleName); + }); + for( let r in this.user.roleNames) { + let role =this.user.roleNames[r]; + let roleValue = this.getRoleNumericValue(role); + for( let role2 in this.assignableRoles) { + //look for another role with a higher soft value. If found, set assigned for this role to false + if( role2 !== role) { + let role2Value = this.getRoleNumericValue(role2); + if( role2Value > roleValue) { + this.assignableRoles[role].assigned = false; + } + } + } + } + this.selectedRole = this.getHighestPriorityRole(); + }); + } + + getHighestPriorityRole(): string { + if(this.user.roles === null || this.user.roles ===0) { + return ''; + } + let selectedRoleName= this.user.roles.reduce((highest, role) => + this.getRoleNumericValue(role.role) > this.getRoleNumericValue(highest.role) + ? role + : highest + ).role; + return selectedRoleName; } } diff --git a/src/app/core/config/config.model.ts b/src/app/core/config/config.model.ts index 4886aff45..e737a7efa 100644 --- a/src/app/core/config/config.model.ts +++ b/src/app/core/config/config.model.ts @@ -98,6 +98,7 @@ export interface Config { registerApplicationCenterNotAllowed?: Array; phpIdUrl?: string; rxNormUrl?: string; + roleSortingConfig?: roleSortConfig; } export interface StagingAreaSettings { @@ -190,4 +191,9 @@ export interface DownloadAsPDF { buttonName?:string; companyName?:string; proprietaryNote?:string; -} \ No newline at end of file +} + +export interface roleSortConfig { + [key: string]: number; +} + diff --git a/src/app/fda/config/config.json b/src/app/fda/config/config.json index d680f7611..171276d6f 100644 --- a/src/app/fda/config/config.json +++ b/src/app/fda/config/config.json @@ -19,7 +19,8 @@ "polymerDisclaimer": "Please do not consider GSRS to be an expert system when registering polymer substances.", "disableJSDraw": false, "registerApplicationCenterNotAllowed": ["CDER", "CBER"], - "apiBaseUrl": "http://localhost:8081/ginas/app/", + "apiBaseUrl": "http://localhost:8080", + "formerApiBaseUrl" : "http://localhost:8081/ginas/app/", "gsrsHomeBaseUrl": "http://localhost:8081/ginas/app/ui/", "apiSSG4mBaseUrl": "http://localhost:8081/ginas/app/", "occasionalApiBasePath": "/ginas/app", @@ -1378,6 +1379,13 @@ "root_codes_CAS", "root_codes_ECHA" ], + "roleSortConfig" : [ + {"roleName" : "null", "numericValue": 0}, + {"roleName" : "ADMIN", "numericValue": 100}, + {"roleName" : "APPROVER", "numericValue": 80}, + {"roleName" : "DATAENTRY", "numericValue": 50}, + {"roleName" : "QUERY", "numericValue": 10} + ], "homeDynamicLinks": [ { "display": "Chemicals", From f2a391f0d96086a13467e2bcfba0cbcc1bed81fa Mon Sep 17 00:00:00 2001 From: Mitch Miller Date: Tue, 31 Mar 2026 21:15:44 -0400 Subject: [PATCH 344/408] removed block of unnecessary code --- .../user-edit-dialog/user-edit-dialog.component.ts | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/src/app/core/admin/user-management/user-edit-dialog/user-edit-dialog.component.ts b/src/app/core/admin/user-management/user-edit-dialog/user-edit-dialog.component.ts index 2a887c868..637ea6f20 100644 --- a/src/app/core/admin/user-management/user-edit-dialog/user-edit-dialog.component.ts +++ b/src/app/core/admin/user-management/user-edit-dialog/user-edit-dialog.component.ts @@ -369,19 +369,6 @@ export class UserEditDialogComponent implements OnInit { this.assignableRoles.sort( (role1: AssignableRole, role2: AssignableRole )=> { return this.getRoleNumericValue(role2.roleName) - this.getRoleNumericValue(role1.roleName); }); - for( let r in this.user.roleNames) { - let role =this.user.roleNames[r]; - let roleValue = this.getRoleNumericValue(role); - for( let role2 in this.assignableRoles) { - //look for another role with a higher soft value. If found, set assigned for this role to false - if( role2 !== role) { - let role2Value = this.getRoleNumericValue(role2); - if( role2Value > roleValue) { - this.assignableRoles[role].assigned = false; - } - } - } - } this.selectedRole = this.getHighestPriorityRole(); }); } From 66886f243cd86d4b8a048988a8d49d9069f39795 Mon Sep 17 00:00:00 2001 From: Jaroslav Iha Date: Wed, 1 Apr 2026 13:52:55 +0200 Subject: [PATCH 345/408] after merge fixes --- src/app/core/auth/auth.service.ts | 13 +++++++------ .../base/pfda-toolbar/pfda-toolbar.component.html | 1 + .../substance-form/substance-form.component.ts | 4 ---- .../substance-ssg2-form.component.ts | 14 ++------------ .../substance-ssg4m-form.component.ts | 2 +- 5 files changed, 11 insertions(+), 23 deletions(-) diff --git a/src/app/core/auth/auth.service.ts b/src/app/core/auth/auth.service.ts index 458ef42f2..e4c726792 100644 --- a/src/app/core/auth/auth.service.ts +++ b/src/app/core/auth/auth.service.ts @@ -233,11 +233,11 @@ export class AuthService { hasRoles(...roles: Array): boolean { const rolesList = [...roles]; - const checkableRoles = this._auth.roles.map((x: Role) => - x.role.toUpperCase(), - ); if (this._auth && this._auth.roles && rolesList && rolesList.length) { + const checkableRoles = this._auth.roles.map((x: Role) => + x.role.toUpperCase(), + ); for (const r of rolesList) { let role = r.toUpperCase(); if (checkableRoles.indexOf(role) === -1) { @@ -288,10 +288,11 @@ export class AuthService { hasAnyRoles(...roles: Array): boolean { const rolesList = [...roles]; - const checkableRoles = this._auth.roles.map((x: Role) => - x.role.toUpperCase(), - ); + if (this._auth && this._auth.roles && rolesList && rolesList.length) { + const checkableRoles = this._auth.roles.map((x: Role) => + x.role.toUpperCase(), + ); for (const r of rolesList) { let role = r.toUpperCase(); if (checkableRoles.indexOf(role) === -1) { diff --git a/src/app/core/base/pfda-toolbar/pfda-toolbar.component.html b/src/app/core/base/pfda-toolbar/pfda-toolbar.component.html index bf49c3f14..7c94c2c06 100644 --- a/src/app/core/base/pfda-toolbar/pfda-toolbar.component.html +++ b/src/app/core/base/pfda-toolbar/pfda-toolbar.component.html @@ -42,6 +42,7 @@ diff --git a/src/app/core/substance-form/substance-form.component.ts b/src/app/core/substance-form/substance-form.component.ts index 370490d15..a42ff68b6 100644 --- a/src/app/core/substance-form/substance-form.component.ts +++ b/src/app/core/substance-form/substance-form.component.ts @@ -98,8 +98,6 @@ export class SubstanceFormComponent implements OnInit, AfterViewInit, AfterViewC definition: SubstanceFormDefinition; user: string; feature: string; - isAdmin: boolean; - isUpdater: boolean; isPfdaVersion: boolean = false; canUpdate: boolean; canMakeAdvancedEdits: boolean; @@ -321,8 +319,6 @@ export class SubstanceFormComponent implements OnInit, AfterViewInit, AfterViewC if (this.configService.configData && this.configService.configData.useApprovalAPI) { this.useApprovalAPI = this.configService.configData.useApprovalAPI; } - this.isAdmin = this.authService.hasRoles('admin'); - this.isUpdater = this.authService.hasAnyRoles('Updater', 'SuperUpdater'); this.isPfdaVersion = this.configService.configData.isPfdaVersion; this.canUpdate = await this.authService.hasSpecificPrivilege("Edit"); this.canMakeAdvancedEdits = await this.authService.hasSpecificPrivilege("Edit Public Data"); diff --git a/src/app/core/substance-ssg2/substance-ssg2-form.component.ts b/src/app/core/substance-ssg2/substance-ssg2-form.component.ts index 41d3f2a12..ddb7da86c 100644 --- a/src/app/core/substance-ssg2/substance-ssg2-form.component.ts +++ b/src/app/core/substance-ssg2/substance-ssg2-form.component.ts @@ -203,11 +203,7 @@ export class SubstanceSsg2FormComponent implements OnInit, AfterViewInit, OnDest if (keys[i].startsWith('gsrs-draft-')) { const entry = JSON.parse(localStorage.getItem(keys[i])); entry.key = keys[i]; - if (this.id && entry.uuid === this.id) { - this.draftCount++; - } else if (!this.id && entry.type === (this.activatedRoute.snapshot.params['type']) && entry.uuid === 'register') { - this.draftCount++; - } + this.draftCount++; this.drafts.push(entry); } @@ -374,13 +370,7 @@ export class SubstanceSsg2FormComponent implements OnInit, AfterViewInit, OnDest if (keys[i].startsWith('gsrs-draft-')) { const entry = JSON.parse(localStorage.getItem(keys[i])); entry.key = keys[i]; - if (this.id && entry.uuid === this.id) { - temp++; - // this.draftCount++; - } else if (!this.id && entry.type === (this.activatedRoute.snapshot.params['type']) && entry.uuid === 'register') { - temp++; - // this.draftCount++; - } + temp++; this.drafts.push(entry); } diff --git a/src/app/core/substance-ssg4m/substance-ssg4m-form.component.ts b/src/app/core/substance-ssg4m/substance-ssg4m-form.component.ts index 9ab56172c..a030f5eac 100644 --- a/src/app/core/substance-ssg4m/substance-ssg4m-form.component.ts +++ b/src/app/core/substance-ssg4m/substance-ssg4m-form.component.ts @@ -1363,7 +1363,7 @@ export class SubstanceSsg4ManufactureFormComponent await this.delay(200); } - const allTabs = document.querySelectorAll(".mat-tab-label"); + const allTabs = document.querySelectorAll(".mat-mdc-tab"); const tabStepView = Array.from(allTabs).find( (tab) => tab.textContent.trim() === "Step View", ) as HTMLElement; From f4fb1cdf0464d3639ad3e46f9def7115a91b2ec8 Mon Sep 17 00:00:00 2001 From: Jaroslav Iha Date: Wed, 1 Apr 2026 14:03:12 +0200 Subject: [PATCH 346/408] configrable ssg4m svg generation --- src/app/core/config/config.model.ts | 1 + .../substance-ssg4m/substance-ssg4m-form.component.ts | 8 +++++--- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/app/core/config/config.model.ts b/src/app/core/config/config.model.ts index 5ccbfca61..7e82d51ea 100644 --- a/src/app/core/config/config.model.ts +++ b/src/app/core/config/config.model.ts @@ -40,6 +40,7 @@ export interface Config { relationshipsVisualizationUri?: string; isPfdaVersion?: boolean; customToolbarComponent?: string; + ssg4mExportSvg?: boolean; disableSessionRefresh?: boolean; sessionRefreshOnActiveUserOnly?: boolean; sessionExpirationWarning?: SessionExpirationWarning; diff --git a/src/app/core/substance-ssg4m/substance-ssg4m-form.component.ts b/src/app/core/substance-ssg4m/substance-ssg4m-form.component.ts index a030f5eac..1033c0c47 100644 --- a/src/app/core/substance-ssg4m/substance-ssg4m-form.component.ts +++ b/src/app/core/substance-ssg4m/substance-ssg4m-form.component.ts @@ -129,6 +129,7 @@ export class SubstanceSsg4ManufactureFormComponent configSsg4Form: any; configSettingReferences = false; private submitSubscription: any = null; + ssg4mExportSvg: boolean; private jsLibScriptUrls = [ `${environment.baseHref || ""}assets/pathway/cola.min.js`, @@ -165,6 +166,7 @@ export class SubstanceSsg4ManufactureFormComponent this.isAuthenticated = this.authService.getUser() !== ""; this.overlayContainer = this.overlayContainerService.getContainerElement(); this.imported = false; + this.ssg4mExportSvg = this.configService.configData.ssg4mExportSvg || false; this.getConfigSettings(); if (this.configSsg4Form) { @@ -1435,7 +1437,7 @@ export class SubstanceSsg4ManufactureFormComponent } async submit(): Promise { - await this.expandStepView(); + this.ssg4mExportSvg && await this.expandStepView(); this.isLoading = true; this.loadingService.setLoading(true); this.approving = false; @@ -1461,8 +1463,8 @@ export class SubstanceSsg4ManufactureFormComponent } }, 8000); - // Export step view as SVG; Disabled for PFDA - await this.exportStepView(document); + // Export step view as SVG; default disabled + this.ssg4mExportSvg && await this.exportStepView(document); // Prepare final JSON and call save endpoint jsonValue = this.prepareFinalJson(); From 74918524c2846bbf21c23f9ecfbff9ff9e507796 Mon Sep 17 00:00:00 2001 From: Jaroslav Iha Date: Wed, 1 Apr 2026 15:11:08 +0200 Subject: [PATCH 347/408] fix pfda toolbar color change --- src/app/core/base/pfda-toolbar/pfda-toolbar.component.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/core/base/pfda-toolbar/pfda-toolbar.component.scss b/src/app/core/base/pfda-toolbar/pfda-toolbar.component.scss index 7efa5fb10..ce2509a0c 100644 --- a/src/app/core/base/pfda-toolbar/pfda-toolbar.component.scss +++ b/src/app/core/base/pfda-toolbar/pfda-toolbar.component.scss @@ -10,7 +10,7 @@ $screenMedium: 1045px; .pfda-toolbar { - background-color: $pfda-navbar-blue; + background-color: $pfda-navbar-blue !important; color: white; font-family: "Lato","Helvetica Neue",Helvetica,Arial,sans-serif; font-size: 13px; From c09f2aa92e113d049df8bb9bea76ed572e3edc8d Mon Sep 17 00:00:00 2001 From: Jaroslav Iha Date: Thu, 2 Apr 2026 14:01:31 +0200 Subject: [PATCH 348/408] update substance-drafts submit flow loading state --- .../substance-drafts/substance-drafts.component.ts | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/app/core/substance-form/substance-drafts/substance-drafts.component.ts b/src/app/core/substance-form/substance-drafts/substance-drafts.component.ts index c18ca1a02..a82e1d08c 100644 --- a/src/app/core/substance-form/substance-drafts/substance-drafts.component.ts +++ b/src/app/core/substance-form/substance-drafts/substance-drafts.component.ts @@ -297,6 +297,8 @@ export class SubstanceDraftsComponent implements OnInit { submitValid() { this.formState = FormState.SUBMISSION; this.isLoading = true; + let completedCount = 0; + const submittableDrafts = this.validatedDrafts.filter(draft => draft.validationResult); this.validatedDrafts.forEach(draft => { if (!draft.validationResult) { draft.submitStatus = SubmissionStatus.CANNOT_BE_SUBMITTED; @@ -310,8 +312,10 @@ export class SubstanceDraftsComponent implements OnInit { this.substanceService.saveSubstance(draft.json.substance, 'import').subscribe(substance => { draft.submitStatus = SubmissionStatus.SUCCESS; draft.fileUrl = substance.fileUrl; - this.isLoading = false; - + completedCount++; + if (completedCount === submittableDrafts.length) { + this.isLoading = false; + } }, error => { draft.submitStatus = SubmissionStatus.ERROR; result.isSuccessfull = false; @@ -320,6 +324,10 @@ export class SubstanceDraftsComponent implements OnInit { } else { result.serverError = error; } + completedCount++; + if (completedCount === submittableDrafts.length) { + this.isLoading = false; + } }); } }) From 7f8c319d95d0c021810cef7a734a4ffcba4479cc Mon Sep 17 00:00:00 2001 From: Mitch Miller Date: Fri, 3 Apr 2026 13:09:04 -0400 Subject: [PATCH 349/408] made display of 2 advanced features ('Set Definition to private' and 'Set Definition to public') dependent on priv Change Definition Visibility --- src/app/core/substance-form/substance-form.component.html | 4 ++-- src/app/core/substance-form/substance-form.component.ts | 4 +++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/app/core/substance-form/substance-form.component.html b/src/app/core/substance-form/substance-form.component.html index 30214df2e..14fd51197 100644 --- a/src/app/core/substance-form/substance-form.component.html +++ b/src/app/core/substance-form/substance-form.component.html @@ -81,10 +81,10 @@ Change Status to pending - + Set Definition to private - + Set Definition to public diff --git a/src/app/core/substance-form/substance-form.component.ts b/src/app/core/substance-form/substance-form.component.ts index c8bee5054..dc76c60f1 100644 --- a/src/app/core/substance-form/substance-form.component.ts +++ b/src/app/core/substance-form/substance-form.component.ts @@ -128,7 +128,8 @@ export class SubstanceFormComponent implements OnInit, AfterViewInit, AfterViewC approvalType = 'lastEditedBy'; previousState: number; useApprovalAPI = false; - featuresOnly = false; + featuresOnly = false; + userCanChangeDefinitionVisibility = false; constructor( private activatedRoute: ActivatedRoute, @@ -330,6 +331,7 @@ export class SubstanceFormComponent implements OnInit, AfterViewInit, AfterViewC this.canMakeAdvancedEdits = await this.authService.hasSpecificPrivilege("Edit Public Data"); this.userCanApprove = await this.authService.hasSpecificPrivilege("Approve Records"); this.userCanMakePublic = await this.authService.hasSpecificPrivilege('Make Records Public'); + this.userCanChangeDefinitionVisibility = await this.authService.hasSpecificPrivilege('Change Definition Visibility'); this.overlayContainer = this.overlayContainerService.getContainerElement(); this.imported = false; From fa7ded1bfa6360b32e6ec74d10ce83b14d196d71 Mon Sep 17 00:00:00 2001 From: Newatia Date: Fri, 3 Apr 2026 14:42:14 -0400 Subject: [PATCH 350/408] updated IVP --- .../applications-browse.component.html | 2 +- .../applications-browse.component.scss | 4 + ...ology-screening-data-import.component.html | 6 +- ...ology-screening-data-import.component.scss | 4 + ...acology-screening-data-import.component.ts | 749 ++++++++---------- .../products-browse.component.html | 2 +- .../products-browse.component.scss | 4 + 7 files changed, 339 insertions(+), 432 deletions(-) diff --git a/src/app/fda/application/applications-browse/applications-browse.component.html b/src/app/fda/application/applications-browse/applications-browse.component.html index 0efb94f87..720d43c2d 100644 --- a/src/app/fda/application/applications-browse/applications-browse.component.html +++ b/src/app/fda/application/applications-browse/applications-browse.component.html @@ -248,7 +248,7 @@
    - diff --git a/src/app/fda/application/applications-browse/applications-browse.component.scss b/src/app/fda/application/applications-browse/applications-browse.component.scss index 449003ff3..4100ccbd3 100644 --- a/src/app/fda/application/applications-browse/applications-browse.component.scss +++ b/src/app/fda/application/applications-browse/applications-browse.component.scss @@ -1213,6 +1213,10 @@ margin:auto; margin-left: 40px; } +.marginleft80px { + margin-left: 80px; +} + .marginleftneg10 { margin-left: -10px; } diff --git a/src/app/fda/invitro-pharmacology/invitro-pharmacology-screening-data-import/invitro-pharmacology-screening-data-import.component.html b/src/app/fda/invitro-pharmacology/invitro-pharmacology-screening-data-import/invitro-pharmacology-screening-data-import.component.html index 461ff7ada..ac6581673 100644 --- a/src/app/fda/invitro-pharmacology/invitro-pharmacology-screening-data-import/invitro-pharmacology-screening-data-import.component.html +++ b/src/app/fda/invitro-pharmacology/invitro-pharmacology-screening-data-import/invitro-pharmacology-screening-data-import.component.html @@ -52,16 +52,16 @@ -

    Importing Assays

    +

    Importing Results Data into Assays

    -

    +

    Saved {{ savedCount }} of {{ totalAssays }} Assays

    -

    {{ progressMessage }}

    +
    diff --git a/src/app/fda/invitro-pharmacology/invitro-pharmacology-screening-data-import/invitro-pharmacology-screening-data-import.component.scss b/src/app/fda/invitro-pharmacology/invitro-pharmacology-screening-data-import/invitro-pharmacology-screening-data-import.component.scss index 30ea867e6..a4bb0bc22 100644 --- a/src/app/fda/invitro-pharmacology/invitro-pharmacology-screening-data-import/invitro-pharmacology-screening-data-import.component.scss +++ b/src/app/fda/invitro-pharmacology/invitro-pharmacology-screening-data-import/invitro-pharmacology-screening-data-import.component.scss @@ -298,6 +298,10 @@ margin-bottom: 20px; } +.textaligncenter { + text-align: center; +} + .margintopneg10px { margin-top: -10px; } diff --git a/src/app/fda/invitro-pharmacology/invitro-pharmacology-screening-data-import/invitro-pharmacology-screening-data-import.component.ts b/src/app/fda/invitro-pharmacology/invitro-pharmacology-screening-data-import/invitro-pharmacology-screening-data-import.component.ts index dd34c33a9..fd1073126 100644 --- a/src/app/fda/invitro-pharmacology/invitro-pharmacology-screening-data-import/invitro-pharmacology-screening-data-import.component.ts +++ b/src/app/fda/invitro-pharmacology/invitro-pharmacology-screening-data-import/invitro-pharmacology-screening-data-import.component.ts @@ -1,5 +1,6 @@ import { Component, ViewChild, TemplateRef, OnInit, OnDestroy } from '@angular/core'; import { ActivatedRoute, Router } from '@angular/router'; +import { HttpClient, HttpParams } from "@angular/common/http"; import { DomSanitizer, SafeUrl } from '@angular/platform-browser'; import { FormBuilder } from '@angular/forms'; import { MatDialog } from '@angular/material/dialog'; @@ -66,10 +67,16 @@ export class InvitroPharmacologyScreeningDataImportComponent implements OnInit { invitroResultsTemp: Array = []; invitroAssayResult: InvitroAssayResult = {}; invitroResultInfo: InvitroAssayResultInformation = {} + firstAssayRemainingScreening: Array = []; requiredFieldMissingArray: Array = []; importDataList: Array = []; importedBulkAssayJson: Array = [{}]; + savedResultInfo: any = null; + + assayToSaveAllList: Array = []; + assayToSaveList: Array = []; + importedAssayJson: any; currentDialogData: any; @@ -91,6 +98,7 @@ export class InvitroPharmacologyScreeningDataImportComponent implements OnInit { errorMessage: string = ''; constructor( + private http: HttpClient, private activatedRoute: ActivatedRoute, private router: Router, private sanitizer: DomSanitizer, @@ -168,9 +176,6 @@ export class InvitroPharmacologyScreeningDataImportComponent implements OnInit { this.getInvitroResults(workbook); - // Validate all data from Excel file - // this.validate(); - } // reader.onload reader.readAsBinaryString(target.files[0]); @@ -697,6 +702,7 @@ export class InvitroPharmacologyScreeningDataImportComponent implements OnInit { delete element["Assay Set"]; delete element["External Assay Source *"]; delete element["External Assay ID *"]; + delete element["Measurements"]; delete element["Assay ID"]; delete element["Test Date (mm/dd/yyyy)"]; delete element["Test Agent Concentration"]; @@ -712,7 +718,7 @@ export class InvitroPharmacologyScreeningDataImportComponent implements OnInit { delete element["Type of Data"]; delete element["Number of Tests"]; delete element["Comments"]; - delete element["Measurements"]; + delete element["External Assay URL/Document Link"]; } }); @@ -736,251 +742,6 @@ export class InvitroPharmacologyScreeningDataImportComponent implements OnInit { return (value === undefined || value == null || value.length <= 0) ? "" : value; } - validate() { - - //this.checkControlAssayFoundInDatabase(); - - this.checkResultAssayFoundInDatabase(); - - /* - let foundallAssays = 'true'; - - // Validate if Assay already Exists into the database - this.invitroResultsTemp.forEach((result, index) => { - if (result.externalAssaySource && result.externalAssayId) { - - const invitroSubscribe = this.invitroPharmacologyService.getAssayByExternalAssay(result.externalAssaySource, result.externalAssayId).subscribe(assay => { - if (assay) { - // Assay Found in the Database - result.assayFoundInDb = 'true'; - - this.createNewScreeningData(assay, result); - // this.invitroAssayFoundInDatabase.push(result); - } - else { - // Assay NOT Found in the Database - result.assayFoundInDb = 'false'; - foundallAssays = 'false'; - } - }, error => { - result.assayFoundInDb = 'Error getting Assay'; - console.log("Import Screeing data - error getting Assay"); - } - ); // subscribe - - this.subscriptions.push(invitroSubscribe); - } - - }); - - // if (found) - // Enable "Import into the Database" button - this.disableImportButton = 'false'; - } - */ - - } - - /* - checkControlAssayFoundInDatabase() { - let foundallAssays = 'true'; - - // Validate if Assay already Exists into the database - this.invitroControlsTemp.forEach((control, index) => { - if (control.externalAssaySource && control.externalAssayId) { - - const invitroSubscribe = this.invitroPharmacologyService.getAssayByExternalAssay(control.externalAssaySource, control.externalAssayId).subscribe(assay => { - if (assay) { - // Assay Found in the Database - control.assayFoundInDb = 'true'; - } - else { - // Assay NOT Found in the Database - control.assayFoundInDb = 'false'; - foundallAssays = 'false'; - } - }, error => { - control.assayFoundInDb = 'Error getting Assay'; - console.log("Import Screeing data - error getting Assay"); - } - ); // subscribe - - this.subscriptions.push(invitroSubscribe); - } else { - foundallAssays = 'false'; - } - - }); - - // Enable "Import into the Database" button - this.disableImportButton = 'false'; - } - */ - - checkResultAssayFoundInDatabase() { - this.resultMessage = 'Checking Assays in the database...'; - this.disableImportButton = 'true'; - - // This object will now carry the source, ID, and the cached assay. - const initialState = { - lastCheckedSource: '', - lastCheckedAssayId: '', // ADDED: To store the last checked ID - cachedAssay: null as InvitroAssayInformation | null - }; - - from(this.invitroResultsTemp).pipe( - // concatMap ensures each result is processed one by one, in order. - concatMap((result, index) => { - // If the result is invalid, skip it. - if (!result.externalAssaySource || !result.externalAssayId) { - result.assayFoundInDb = 'false'; - return of({ result, status: 'skipped' }); - } - - // UPDATED: Check if both source AND ID match the last processed item. - if ( - initialState.lastCheckedSource === result.externalAssaySource && - initialState.lastCheckedAssayId === result.externalAssayId && - initialState.cachedAssay - ) { - // If YES, reuse the cached assay. No database call is needed. - console.log(`Reusing cached assay for source/ID: ${result.externalAssaySource}/${result.externalAssayId}`); - result.assayFoundInDb = 'true'; - this.createNewScreeningData(_.cloneDeep(initialState.cachedAssay), result); - return of({ result, status: 'reused' }); - } else { - // If NO, we must make a new database call. - console.log(`Fetching new assay for source/ID: ${result.externalAssaySource}/${result.externalAssayId}`); - - // Update the state for the next iteration with the current source and ID. - initialState.lastCheckedSource = result.externalAssaySource; - initialState.lastCheckedAssayId = result.externalAssayId; // ADDED: Update the ID in our state - - return this.invitroPharmacologyService.getAssayByExternalAssay( - result.externalAssaySource, - result.externalAssayId - ).pipe( - map(dbAssay => { - if (dbAssay) { - // Assay found: update the result and cache it. - result.assayFoundInDb = 'true'; - initialState.cachedAssay = _.cloneDeep(dbAssay); // Cache the new assay - this.createNewScreeningData(dbAssay, result); - } else { - // Assay not found: update result and clear the cache. - result.assayFoundInDb = 'false'; - initialState.cachedAssay = null; // Invalidate cache - } - return { result, status: 'fetched' }; - }), - catchError(error => { - console.log("Import Screening data - error getting Assay", error); - result.assayFoundInDb = 'Error getting Assay'; - initialState.cachedAssay = null; // Invalidate cache on error - return of({ result, status: 'error' }); - }) - ); - } - }), - // Collect all processed results into a single array. - toArray() - ).subscribe(processedResults => { - // This block executes ONCE after all items have been processed sequentially. - const allAssaysFound = this.invitroResultsTemp.every(r => r.assayFoundInDb === 'true'); - - this.resultMessage = allAssaysFound - ? 'All assays verified.' - : 'Some assays were not found or could not be verified. Please review.'; - - this.disableImportButton = 'false'; - console.log('Sequential check complete.'); - }); - } - - /* - checkResultAssayFoundInDatabase_2() { - let localAssay: InvitroAssayInformation; - let checkedExternalSource = ''; - let checkedExternalAssayId = ''; - let foundallAssays = 'true'; - this.resultMessage = ''; - - // Validate if Assay already Exists into the database - this.invitroResultsTemp.forEach((result, index) => { - - if (result.externalAssaySource && result.externalAssayId) { - - this.resultMessage = 'Checking Assays in the database ...'; - - if (checkedExternalSource !== result.externalAssaySource) { - checkedExternalSource = result.externalAssaySource; - - const invitroSubscribe = this.invitroPharmacologyService.getAssayByExternalAssay(result.externalAssaySource, result.externalAssayId).subscribe(assay => { - if (assay) { - - localAssay = _.cloneDeep(assay); - // Assay Found in the Database - result.assayFoundInDb = 'true'; - - this.createNewScreeningData(assay, result); - } - else { - // Assay NOT Found in the Database - result.assayFoundInDb = 'false'; - foundallAssays = 'false'; - } - - if (this.invitroResultsTemp.length === (index + 1)) { - this.resultMessage = ''; - } - - }, error => { - result.assayFoundInDb = 'Error getting Assay'; - console.log("Import Screeing data - error getting Assay"); - } - ); // subscribe - - this.subscriptions.push(invitroSubscribe); - } // checkedExternalSource !== result.externalAssaySource - else { - // Assay Found in the Database - result.assayFoundInDb = 'true'; - - this.createNewScreeningData(localAssay, result); - } // - } else { - foundallAssays = 'false'; - } - - }); - - // Enable "Import into the Database" button - this.disableImportButton = 'false'; - } - */ - - createNewScreeningData(assay: InvitroAssayInformation, resultElement: any) { - // Create new screening object - const screening: InvitroAssayScreening = {}; - - const date = new Date(); - let importfilename = "import_screening_" + moment(date).format('MMM-DD-YYYY_H-mm-ss'); - - screening.screeningImportFileName = importfilename; - - // Create new Invitro Control and Result - screening.invitroControls = this.createNewInvitroControl(resultElement); - screening.invitroAssayResult = this.createInvitroResult(resultElement); - - // Push screening to Assay - assay.invitroAssayScreenings.push(screening); - - // Push screening to Assay - this.invitroAssayScreenings.push(screening); - - this.assayToSave.push(assay); - } - createInvitroReference(object: any): any { let tempObject = _.cloneDeep(object); @@ -1046,72 +807,79 @@ export class InvitroPharmacologyScreeningDataImportComponent implements OnInit { return newObject; } - /* - importAssayJSONIntoDatabase2() { - - let savedResultInfo: any; + createNewScreeningData(assay: InvitroAssayInformation, resultElement: any, index?: number) { + // This object will now carry the Assay and new Screening Results and Controls. + let newAssayToSave = { + assay: null as InvitroAssayInformation | null, + newScreening: null as InvitroAssayScreening + }; - if (this.invitroAssayScreenings.length > 0) { + // Create new screening object + const screening: InvitroAssayScreening = {}; - let firstScreeningToSave = this.invitroAssayScreenings[0]; - // let assayId = firstScreeningToSave._ownerId; - let scrubScreening = this.scrub(firstScreeningToSave); + const date = new Date(); + let importfilename = "import_screening_" + moment(date).format('MMM-DD-YYYY_H-mm-ss'); - firstScreeningToSave.invitroAssayResultInformation = {}; - firstScreeningToSave.invitroAssayResultInformation.invitroReferences[0] = this.invitroReference; + screening.screeningImportFileName = importfilename; + screening.assaySet = resultElement.assaySet; + screening.invitroAssayResultInformation = {}; - const saveFirstScreeningSubscribe = this.invitroPharmacologyService.saveScreening(scrubScreening, assayId).subscribe(responseFirstScreening => { - if (responseFirstScreening) { - if (responseFirstScreening.id) { + // Create new Invitro Control and Result + screening.invitroControls = this.createNewInvitroControl(resultElement); + screening.invitroAssayResult = this.createInvitroResult(resultElement); - //if (responseScreening.invitroAssayScreenings.length > 0) { - // let screening = responseAssay.invitroAssayScreenings[responseAssay.invitroAssayScreenings.length - 1]; + newAssayToSave.assay = assay; + newAssayToSave.newScreening = screening; - // Check if InvitroAssayResultInformation data in the first screening was saved - // successfully into the database - savedResultInfo = responseFirstScreening.invitroAssayResultInformation; + this.assayToSaveAllList.push(newAssayToSave); - // if successful, save the remaining screening into the database. - if (savedResultInfo) { + // Push screening to Assay + this.invitroAssayScreenings.push(screening); - // remove the first screening, save the remaining screeing into the database - this.invitroAssayScreenings.splice(0, 1); + } - // Copy the screening to new variable - let remainingScreening = _.cloneDeep(this.invitroAssayScreenings); + combineAssaysAndScreenings() { + // Use a Map to store the combined assays, with the assay ID as the key. + const assayMap = new Map(); - remainingScreening.forEach(screening => { - if (screening) { + // Iterate over each item in the input list. + for (const currentItem of this.assayToSaveAllList) { + // Ensure the item and its assay/ID are valid before processing. + if (!currentItem?.assay?.id) { + console.warn("Skipping an item with a null or invalid assay/ID.", currentItem); + continue; + } - // screening.invitroAssayResultInformation = savedResultInfo - screening.invitroAssayResultInformation = {}; - screening.invitroAssayResultInformation.id = savedResultInfo.id; - // screening.invitroAssayResultInformation.internalVersion = savedResultInfo.internalVersion; + const assayId = currentItem.assay.id; - //let remainingAssayId = screening._ownerId; - let remainingScrubScreening = this.scrub(screening); + // Check if we have already processed an assay with this ID. + if (!assayMap.has(assayId)) { + // If this is the first time seeing this assay ID, create a new entry in the map. + const newAssay = { ...currentItem.assay }; - const saveSubscribe = this.invitroPharmacologyService.saveScreening(remainingScrubScreening, remainingAssayId).subscribe(response => { - if (response) { + // Add the current screening to the new assay if it exists. + if (currentItem.newScreening) { + newAssay.invitroAssayScreenings.push(currentItem.newScreening); + } - if (response.id) { - } - } // if response - }); - this.subscriptions.push(saveSubscribe); + // Add the new, combined assay object to our map. + assayMap.set(assayId, newAssay); + } else { + // If the assay already exists in our map, retrieve it. + const existingAssay = assayMap.get(assayId)!; - } // if (screening) - }); // forloop remainingScreening + // Add the new screening to the existing assay's screening list if it exists. + if (currentItem.newScreening) { + existingAssay.invitroAssayScreenings?.push(currentItem.newScreening); + } + } + } - } // if (savedResultInfo) + // The map now contains all unique assays with their screenings combined. + // Convert the values of the map to an array and return it. + this.assayToSaveList = Array.from(assayMap.values()); - } // if (responseFirstScreening.id) - } // if (responseFirstScreening) - }); // save one Assay record first - this.subscriptions.push(saveFirstScreeningSubscribe); - } } - */ getSubstanceByNameExactMatch(ingredientName: string, fieldName?: string) { let found = false; @@ -1175,7 +943,7 @@ export class InvitroPharmacologyScreeningDataImportComponent implements OnInit { if (found == false) { this.setValidationMessage(fieldName + ' "' + ingredientName + '" does not exist in the database. Please register this substance first and then import again', validationMessages, index); } - + if (fieldName == this.TARGET_NAME) { this.targetNameCheckCompleted = true; } else if (fieldName == this.HUMAN_HOMOLOG_TARGET) { @@ -1205,72 +973,298 @@ export class InvitroPharmacologyScreeningDataImportComponent implements OnInit { this.subscriptions.push(substanceSubscribe); } + validate() { + this.assayToSaveAllList = []; + this.assayToSaveList = []; + this.savedResultInfo = null; + + this.resultMessage = 'Checking Assays in the database...'; + this.disableImportButton = 'true'; + + // This object will now carry the source, ID, and the cached assay. + const initialState = { + lastCheckedSource: '', + lastCheckedAssayId: '', // ADDED: To store the last checked ID + cachedAssay: null as InvitroAssayInformation | null + }; + + from(this.invitroResultsTemp).pipe( + // concatMap ensures each result is processed one by one, in order. + concatMap((result, index) => { + // If the result is invalid, skip it. + if (!result.externalAssaySource || !result.externalAssayId) { + result.assayFoundInDb = 'false'; + return of({ result, status: 'skipped' }); + } + + // UPDATED: Check if both source AND ID match the last processed item. + if ( + initialState.lastCheckedSource === result.externalAssaySource && + initialState.lastCheckedAssayId === result.externalAssayId && + initialState.cachedAssay + ) { + // If YES, reuse the cached assay. No database call is needed. + console.log(`Reusing cached assay for source/ID: ${result.externalAssaySource}/${result.externalAssayId}`); + result.assayFoundInDb = 'true'; + + this.createNewScreeningData(initialState.cachedAssay, result, index); + return of({ result, status: 'reused' }); + } else { + // If NO, we must make a new database call. + console.log(`Fetching new assay for source/ID: ${result.externalAssaySource}/${result.externalAssayId}`); + + // Update the state for the next iteration with the current source and ID. + initialState.lastCheckedSource = result.externalAssaySource; + initialState.lastCheckedAssayId = result.externalAssayId; // ADDED: Update the ID in our state + + return this.invitroPharmacologyService.getAssayByExternalAssay( + result.externalAssaySource, + result.externalAssayId + ).pipe( + map(dbAssay => { + if (dbAssay) { + // Assay found: update the result and cache it. + result.assayFoundInDb = 'true'; + // cached Assay will also have new Screening data + initialState.cachedAssay = _.cloneDeep(dbAssay); // Cache the new assay + + this.createNewScreeningData(dbAssay, result); + } else { + // Assay not found: update result and clear the cache. + result.assayFoundInDb = 'false'; + initialState.cachedAssay = null; // Invalidate cache + } + return { result, status: 'fetched' }; + }), + catchError(error => { + console.log("Import Screening data - error getting Assay", error); + result.assayFoundInDb = 'Error getting Assay'; + initialState.cachedAssay = null; // Invalidate cache on error + return of({ result, status: 'error' }); + }) + ); + } + }), + // Collect all processed results into a single array. + toArray() + ).subscribe(processedResults => { + // This block executes ONCE after all items have been processed sequentially. + const allAssaysFound = this.invitroResultsTemp.every(r => r.assayFoundInDb === 'true'); + + this.resultMessage = allAssaysFound + ? 'All assays verified.' + : 'Some assays were not found or could not be verified. Please review.'; + + this.disableImportButton = 'false'; + console.log('Sequential check complete.'); + }); + } + importAssayJSONIntoDatabase() { - if (!this.assayToSave || this.assayToSave.length === 0) { - console.log('No assays to save.'); + + this.combineAssaysAndScreenings(); + + this.importFirstScreeningResult(); + } + + importFirstScreeningResult() { + + let assayApiUrlList: any = []; + + const params = new HttpParams(); + const options = { + params: params, + type: "JSON", + headers: { + "Content-type": "application/json", + }, + }; + + const url = this.invitroPharmacologyService.apiBaseUrlWithInvitroPharmEntityUrl; + + if (!this.assayToSaveList || this.assayToSaveList.length === 0) { + console.log('No Assays to save.'); return; } + if (this.assayToSaveList && this.assayToSaveList.length > 0) { + + // Get First Record from the Array + let firstAssay = _.cloneDeep(this.assayToSaveList[0]); + + // Find the index of the first screening to keep. + if (firstAssay.invitroAssayScreenings && firstAssay.invitroAssayScreenings.length > 0) { + const indexToKeep = firstAssay.invitroAssayScreenings.findIndex(screening => !screening.id); + + // Check if a screening to keep was found. + if (indexToKeep > -1) { + // Keep the first screening without an ID, and remove the rest of screening that do not have Ids. + this.firstAssayRemainingScreening = firstAssay.invitroAssayScreenings.splice(indexToKeep + 1); + } + + // For the first assay, attach the full result information object. + this.invitroResultInfo.invitroReferences = this.invtroReferences; + this.invitroResultInfo.invitroLaboratory = this.invitroLaboratory; + this.invitroResultInfo.invitroSponsor = this.invitroSponsor; + this.invitroResultInfo.invitroSponsorReport = this.invitroSponsorReport; + this.invitroResultInfo.invitroTestAgent = this.invitroTestAgent; + + firstAssay.invitroAssayScreenings[firstAssay.invitroAssayScreenings.length - 1].invitroAssayResultInformation = this.invitroResultInfo; + + const apiUrl = this.http + .put(url, firstAssay, options) + .pipe( + catchError((error) => { + throw error; + }), + ); + + // Rest API Urls for forkJoin + assayApiUrlList.push(apiUrl); + + let savedCount = 0; + + if (assayApiUrlList && assayApiUrlList.length > 0) { + // Save Assays into the database + forkJoin(assayApiUrlList).subscribe( + (results) => { + let resultList: any = []; + + resultList = results; + + // return list of array of the result + resultList.forEach((result) => { + if (result.id) { + savedCount = savedCount + 1; + + if (result.invitroAssayScreenings && result.invitroAssayScreenings.length > 0) { + if (result.invitroAssayScreenings[result.invitroAssayScreenings.length - 1].invitroAssayResultInformation) { + if (result.invitroAssayScreenings[result.invitroAssayScreenings.length - 1].invitroAssayResultInformation.id) { + this.savedResultInfo = result.invitroAssayScreenings[result.invitroAssayScreenings.length - 1].invitroAssayResultInformation; + + + let newAssay = _.clone(result); + + if (this.firstAssayRemainingScreening != null && this.firstAssayRemainingScreening.length > 0) { + this.firstAssayRemainingScreening.forEach(screening => { + if (screening) { + screening.invitroAssayResultInformation = this.savedResultInfo; + + newAssay.invitroAssayScreenings.push(screening); + } + }); + + this.assayToSaveList[0] = newAssay; + } + } + } + } + } + }); + + // if all the records are saved, refresh the page + if (savedCount == assayApiUrlList.length) { + + // Save remaining of Results in this Array and other Arrays + this.importRemainingScreeingResults(); + } + + }, + (error) => { + this.errorMessage = "There was a problem importing Assay Results from Excel file to Database"; + this.loadingService.setLoading(false); + alert("ERROR: Something went wrong importing Assay from Excel file to Database"); + }, + ); // forkJoin + } + } + } // assayToSaveList length > 0 + } + + importRemainingScreeingResults() { + + if (!this.assayToSaveList || this.assayToSaveList.length === 0) { + console.log('No Assays to save.'); + return; + } + + // This object will now carry the source, ID, and the cached assay. + const initialState = { + lastCheckedSource: '', + lastCheckedAssayId: '', // ADDED: To store the last checked ID + cachedAssay: null as InvitroAssayInformation | null + }; + // This will hold the result information from the first saved assay for final navigation. - let savedResultInfo: any = null; - // --- A. Initialize state and open the dialog immediately --- + // Initialize state and open the dialog immediately --- this.savedCount = 0; - this.totalAssays = this.assayToSave.length; + this.totalAssays = this.assayToSaveList.length; this.progressMessage = 'Preparing to save assays...'; this.isComplete = false; this.isError = false; + let assayReadyToSave: InvitroAssayInformation; + const dialogRef = this.dialog.open(this.progressDialogTemplateRef, { width: '500px', disableClose: true // Prevent user from closing it while the process is running. }); // --- B. Start the sequential save stream using RxJS --- - from(this.assayToSave).pipe( + from(this.assayToSaveList).pipe( // 'concatMap' ensures each assay is processed one by one, waiting for the previous save to complete. - concatMap((assay, index) => { + concatMap((assayToSave, index) => { // Update the dialog message before each save attempt. this.progressMessage = `Saving assay ${index + 1} of ${this.totalAssays}...`; + assayReadyToSave = _.cloneDeep(assayToSave); + // } + + // Check if index is 0 const isFirstAssay = (index === 0); - const assayToSave = _.cloneDeep(assay); - const lastScreeningIndex = assayToSave.invitroAssayScreenings.length - 1; // Prepare the payload based on whether it's the first assay or a subsequent one. if (isFirstAssay) { - // For the first assay, attach the full result information object. - this.invitroResultInfo.invitroReferences = this.invtroReferences; - this.invitroResultInfo.invitroLaboratory = this.invitroLaboratory; - this.invitroResultInfo.invitroSponsor = this.invitroSponsor; - this.invitroResultInfo.invitroSponsorReport = this.invitroSponsorReport; - this.invitroResultInfo.invitroTestAgent = this.invitroTestAgent; - assayToSave.invitroAssayScreenings[lastScreeningIndex].invitroAssayResultInformation = this.invitroResultInfo; - } else { - // For all subsequent assays, link them to the first one's result info ID. - if (!savedResultInfo?.id) { - return throwError(() => new Error('Cannot save subsequent assay: Primary result information ID is missing.')); + + const indexToKeep = assayReadyToSave.invitroAssayScreenings.findIndex(screening => !screening.id); + + // Check if a screening to keep was found. + if (indexToKeep > -1) { + // Keep the first screening without an ID, and remove the rest of screening that do not have Ids. + assayReadyToSave.invitroAssayScreenings.splice(indexToKeep, 1); } - // assayToSave.invitroAssayScreenings[lastScreeningIndex].invitroAssayResultInformation = { id: savedResultInfo.id }; - assayToSave.invitroAssayScreenings[lastScreeningIndex].invitroAssayResultInformation = savedResultInfo; } + // Assign savedResultInfo + assayReadyToSave.invitroAssayScreenings.forEach((screening, indexScreening) => { + if (screening) { + if (screening.invitroAssayResultInformation == null) { + screening.invitroAssayResultInformation = {}; + screening.invitroAssayResultInformation = this.savedResultInfo; + } + if (screening.invitroAssayResultInformation && !screening.invitroAssayResultInformation.id) { + } + } + }); + // Set the assay on the service (following your existing stateful pattern). - this.invitroPharmacologyService.assay = assayToSave; + this.invitroPharmacologyService.assay = assayReadyToSave; // Return the save observable. concatMap will subscribe and wait for it to complete. return this.invitroPharmacologyService.saveAssay().pipe( // 'tap' is used for side-effects, like updating the UI, without altering the stream. tap(savedAssay => { + + // Update the state for the next iteration with the current source and ID. + initialState.lastCheckedSource = savedAssay.externalAssaySource; + initialState.lastCheckedAssayId = savedAssay.externalAssayId; // ADDED: Update the ID in our state + initialState.cachedAssay = _.cloneDeep(savedAssay); // Cache the new assay + this.savedCount = index + 1; // Update the count for the dialog. - // If this was the first assay, capture its result info for the final navigation. - if (isFirstAssay) { - const screening = savedAssay.invitroAssayScreenings[savedAssay.invitroAssayScreenings.length - 1]; - savedResultInfo = screening.invitroAssayResultInformation; - } }) - ); + ); // return }) ).subscribe({ // 'next' is handled by tap, so this can be empty. It's called after each successful save. @@ -1281,7 +1275,6 @@ export class InvitroPharmacologyScreeningDataImportComponent implements OnInit { this.isError = true; this.errorMessage = err.message || 'An unknown error occurred during the save process.'; this.progressMessage = 'The import process failed.'; - // The 'Close' button on the dialog will now be enabled due to the [disabled] binding. }, // --- D. Handle successful completion of the entire stream --- @@ -1295,110 +1288,12 @@ export class InvitroPharmacologyScreeningDataImportComponent implements OnInit { this.invitroPharmacologyService.bypassUpdateCheck(); this.router.routeReuseStrategy.shouldReuseRoute = () => false; this.router.onSameUrlNavigation = 'reload'; - this.router.navigate(['/invitro-pharm/', savedResultInfo.id, 'edit']); + this.router.navigate(['/invitro-pharm/', this.savedResultInfo.id, 'edit']); }); } }); } - - /* - importAssayJSONIntoDatabase_2ORIG() { - - this.loadingService.setLoading(true); - - let savedResultInfo: any; - if (this.assayToSave.length > 0) { - - let firstAssayToSave = this.assayToSave[0]; - - // Set Reference to Result Information Object - this.invitroResultInfo.invitroReferences = this.invtroReferences; - this.invitroResultInfo.invitroLaboratory = this.invitroLaboratory; - this.invitroResultInfo.invitroSponsor = this.invitroSponsor; - this.invitroResultInfo.invitroSponsorReport = this.invitroSponsorReport; - this.invitroResultInfo.invitroTestAgent = this.invitroTestAgent; - - // Set invitroAssayResultInformation in first Assay Record - firstAssayToSave.invitroAssayScreenings[firstAssayToSave.invitroAssayScreenings.length - 1].invitroAssayResultInformation = this.invitroResultInfo; - - // Assign assay to Servive assay - this.invitroPharmacologyService.assay = firstAssayToSave; - - const saveOneAssaySubscribe = this.invitroPharmacologyService.saveAssay().subscribe(responseAssay => { - if (responseAssay) { - if (responseAssay.id) { - if (responseAssay.invitroAssayScreenings.length > 0) { - - // Get the last screening from the returned/saved Assay - let screening = responseAssay.invitroAssayScreenings[responseAssay.invitroAssayScreenings.length - 1]; - - savedResultInfo = screening.invitroAssayResultInformation; - - // First invitroAssayResultInformation has been saved. Get the id - if (savedResultInfo) { - - // Remove/delete the first Assay from the list - this.assayToSave.splice(0, 1); - - // CLone/Copy the remaining assay - let remainingBulkAssay = _.cloneDeep(this.assayToSave); - - remainingBulkAssay.forEach(assay => { - if (assay) { - assay.invitroAssayScreenings.forEach(screening => { - // Assign the first invitroAssayResultInformation here. - - // screening.invitroAssayResultInformation = savedResultInfo; - - // screening.invitroAssayResultInformation = {}; - // screening.invitroAssayResultInformation.id = savedResultInfo.id; - // screening.invitroAssayResultInformation.internalVersion = savedResultInfo.internalVersion; - - }); - - assay.invitroAssayScreenings[assay.invitroAssayScreenings.length - 1].invitroAssayResultInformation = savedResultInfo; - //assay.invitroAssayScreenings[assay.invitroAssayScreenings.length - 1].invitroAssayResultInformation = {}; - //assay.invitroAssayScreenings[assay.invitroAssayScreenings.length - 1].invitroAssayResultInformation.id = savedResultInfo.id; - - // Assign the assay to service assay - this.invitroPharmacologyService.assay = assay; - - const saveSubscribe = this.invitroPharmacologyService.saveAssay().subscribe(response => { - if (response) { - - setTimeout(() => { - // // this.showSubmissionMessages = false; - // this.submissionMessage = ''; - if (response.id) { - this.loadingService.setLoading(false); - - this.invitroPharmacologyService.bypassUpdateCheck(); - const id = response.id; - this.router.routeReuseStrategy.shouldReuseRoute = () => false; - this.router.onSameUrlNavigation = 'reload'; - this.router.navigate(['/invitro-pharm/', savedResultInfo.id, 'edit']); - } - }, 4000); - } // if response remaining assays - - }); - this.subscriptions.push(saveSubscribe); - - } // if assay exists in remainingBulk list - - }); // forloop remainingBulk - } // if savedResultInfo exists - - } // if responseAssay.invitroAssayScreenings.length > 0 - } - } - }); // save one Assay record first - this.subscriptions.push(saveOneAssaySubscribe); - } - } - */ - showJSON(): void { const date = new Date(); let jsonFilename = 'invitro_pharm_bulk_assay_screenings_' + moment(date).format('MMM-DD-YYYY_H-mm-ss'); diff --git a/src/app/fda/product/products-browse/products-browse.component.html b/src/app/fda/product/products-browse/products-browse.component.html index 0123f854c..36815daa9 100644 --- a/src/app/fda/product/products-browse/products-browse.component.html +++ b/src/app/fda/product/products-browse/products-browse.component.html @@ -261,7 +261,7 @@
    - diff --git a/src/app/fda/product/products-browse/products-browse.component.scss b/src/app/fda/product/products-browse/products-browse.component.scss index 2ec258f20..c4199185e 100644 --- a/src/app/fda/product/products-browse/products-browse.component.scss +++ b/src/app/fda/product/products-browse/products-browse.component.scss @@ -984,6 +984,10 @@ margin-left: 40px; } +.marginleft80px { + margin-left: 80px; +} + .marginright10px { margin-right: 10px; } From 2b9239026b28e952f0b90ef0123b97216c8a521d Mon Sep 17 00:00:00 2001 From: Mitch Miller Date: Mon, 6 Apr 2026 16:40:37 -0400 Subject: [PATCH 351/408] some defensive coding when saving users --- .../user-edit-dialog/user-edit-dialog.component.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/app/core/admin/user-management/user-edit-dialog/user-edit-dialog.component.ts b/src/app/core/admin/user-management/user-edit-dialog/user-edit-dialog.component.ts index 637ea6f20..56ce11842 100644 --- a/src/app/core/admin/user-management/user-edit-dialog/user-edit-dialog.component.ts +++ b/src/app/core/admin/user-management/user-edit-dialog/user-edit-dialog.component.ts @@ -351,7 +351,9 @@ export class UserEditDialogComponent implements OnInit { (this.configService.configData.roleSortingConfig && this.configService.configData.roleSortingConfig["null"] != null ) ? this.configService.configData.roleSortingConfig["null"] : 0; - if(roleName.toUpperCase() in this.configService.configData.roleSortingConfig) { + let roleUpper = roleName?.toUpperCase(); + if (roleUpper && this.configService.configData?.roleSortingConfig + && Object.hasOwn(this.configService.configData.roleSortingConfig, roleUpper)) { return this.configService.configData.roleSortingConfig[roleName.toUpperCase()]; } return 1; From 345babb2cdd104150cbd712e01de4b60115bcdfb Mon Sep 17 00:00:00 2001 From: Mitch Miller Date: Mon, 6 Apr 2026 20:14:25 -0400 Subject: [PATCH 352/408] added a check to make sure a role is selected for a user before saving --- .../user-edit-dialog.component.ts | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/src/app/core/admin/user-management/user-edit-dialog/user-edit-dialog.component.ts b/src/app/core/admin/user-management/user-edit-dialog/user-edit-dialog.component.ts index 56ce11842..df855f3b7 100644 --- a/src/app/core/admin/user-management/user-edit-dialog/user-edit-dialog.component.ts +++ b/src/app/core/admin/user-management/user-edit-dialog/user-edit-dialog.component.ts @@ -165,6 +165,9 @@ export class UserEditDialogComponent implements OnInit { if (this.changePassword && this.newPassword !== '' ) { this.isError = true; this.message = 'Cancel or submit new password to save other changes'; + } else if(!this.selectedRole || this.selectedRole===null || this.selectedRole.length ===0){ + this.message = "Please select a role for this user"; + return; } else { this.isError = false; const rolesArr = [this.selectedRole]; @@ -176,6 +179,11 @@ export class UserEditDialogComponent implements OnInit { } }); + if(!this.selectedRole || this.selectedRole===null || this.selectedRole.length ===0){ + this.message = "Please select a role for this user"; + return; + } + if (this.newGroup && this.newGroup !== '') { groups.push(this.newGroup); } @@ -225,6 +233,11 @@ export class UserEditDialogComponent implements OnInit { addUser(): void { this.isError = false; if (this.newPassword === this.newPasswordConfirm) { + if(!this.selectedRole || this.selectedRole===null || this.selectedRole.length ===0){ + this.message = "Please select a role for this user"; + return; + } + const rolesArr = [this.selectedRole]; const groups = []; this.groups.forEach(group => { @@ -376,7 +389,7 @@ export class UserEditDialogComponent implements OnInit { } getHighestPriorityRole(): string { - if(this.user.roles === null || this.user.roles ===0) { + if(! this.user.roles || this.user.roles === null || this.user.roles.length ===0) { return ''; } let selectedRoleName= this.user.roles.reduce((highest, role) => From 2bf95e81786472b96b4de0088076560e54279aeb Mon Sep 17 00:00:00 2001 From: Newatia Date: Mon, 6 Apr 2026 22:22:27 -0400 Subject: [PATCH 353/408] fixed application and IVP --- .../application-form.component.ts | 19 ++- .../invitro-pharmacology-form.component.scss | 2 +- ...ology-screening-data-import.component.html | 12 +- ...acology-screening-data-import.component.ts | 124 +++++++++--------- 4 files changed, 76 insertions(+), 81 deletions(-) diff --git a/src/app/fda/application/application-form/application-form.component.ts b/src/app/fda/application/application-form/application-form.component.ts index 0aab8a2b6..9aa2fb459 100644 --- a/src/app/fda/application/application-form/application-form.component.ts +++ b/src/app/fda/application/application-form/application-form.component.ts @@ -31,10 +31,10 @@ import { Application, ValidationMessage } from '../model/application.model'; import { forEach } from 'lodash'; @Component({ - selector: 'app-application-form', - templateUrl: './application-form.component.html', - styleUrls: ['./application-form.component.scss'], - standalone: false + selector: 'app-application-form', + templateUrl: './application-form.component.html', + styleUrls: ['./application-form.component.scss'], + standalone: false }) export class ApplicationFormComponent implements OnInit, AfterViewInit, OnDestroy { @@ -56,7 +56,7 @@ export class ApplicationFormComponent implements OnInit, AfterViewInit, OnDestro submitDateMessage = ''; statusDateMessage = ''; appForm: FormGroup; - canDelete:boolean = false; + canDelete: boolean = false; regAppCenterNotAllowedConfig: Array; constructor( @@ -253,12 +253,12 @@ export class ApplicationFormComponent implements OnInit, AfterViewInit, OnDestro // Validate Ingredient Average, Low, High, LowLimit, HighLimit should be integer/number elementProd.applicationIngredientList.forEach(elementIngred => { if (elementIngred != null) { - + // Trim Applicant Ingredient Name if (elementIngred.applicantIngredName) { elementIngred.applicantIngredName = elementIngred.applicantIngredName.trim(); } - + if (elementIngred.average) { if (this.isNumber(elementIngred.average) === false) { this.setValidationMessage('Average must be a number'); @@ -529,6 +529,8 @@ export class ApplicationFormComponent implements OnInit, AfterViewInit, OnDestro const isValid = this.validateSubmitDateWithStatusDate(this.application.submitDate, this.application.statusDate); if (isValid === false) { this.submitDateMessage = 'Submit Date should be earlier than Status Date'; + } else { + this.submitDateMessage = ''; } } } @@ -545,7 +547,10 @@ export class ApplicationFormComponent implements OnInit, AfterViewInit, OnDestro } else { isValid = this.validateSubmitDateWithStatusDate(this.application.submitDate, this.application.statusDate); if (isValid === false) { + // Submit Date is not before Status Date this.submitDateMessage = 'Submit Date should be earlier than Status Date'; + } else { + this.submitDateMessage = ''; } } } diff --git a/src/app/fda/invitro-pharmacology/invitro-pharmacology-form/invitro-pharmacology-form.component.scss b/src/app/fda/invitro-pharmacology/invitro-pharmacology-form/invitro-pharmacology-form.component.scss index c004f9028..29fe62cfa 100644 --- a/src/app/fda/invitro-pharmacology/invitro-pharmacology-form/invitro-pharmacology-form.component.scss +++ b/src/app/fda/invitro-pharmacology/invitro-pharmacology-form/invitro-pharmacology-form.component.scss @@ -255,7 +255,7 @@ } .actions-container { - max-width: 1028px; + max-width: 1100px; width: 100%; background-color: var(--regular-white-color); padding: 10px; diff --git a/src/app/fda/invitro-pharmacology/invitro-pharmacology-screening-data-import/invitro-pharmacology-screening-data-import.component.html b/src/app/fda/invitro-pharmacology/invitro-pharmacology-screening-data-import/invitro-pharmacology-screening-data-import.component.html index ac6581673..7e9742a9a 100644 --- a/src/app/fda/invitro-pharmacology/invitro-pharmacology-screening-data-import/invitro-pharmacology-screening-data-import.component.html +++ b/src/app/fda/invitro-pharmacology/invitro-pharmacology-screening-data-import/invitro-pharmacology-screening-data-import.component.html @@ -55,13 +55,9 @@

    Importing Results Data into Assays

    - -

    - Saved {{ savedCount }} of {{ totalAssays }} Assays -

    - +

    {{ progressMessage }}

    @@ -70,12 +66,6 @@

    An error occurred:

    -
    - - -
    diff --git a/src/app/fda/invitro-pharmacology/invitro-pharmacology-screening-data-import/invitro-pharmacology-screening-data-import.component.ts b/src/app/fda/invitro-pharmacology/invitro-pharmacology-screening-data-import/invitro-pharmacology-screening-data-import.component.ts index fd1073126..1cbf6c38b 100644 --- a/src/app/fda/invitro-pharmacology/invitro-pharmacology-screening-data-import/invitro-pharmacology-screening-data-import.component.ts +++ b/src/app/fda/invitro-pharmacology/invitro-pharmacology-screening-data-import/invitro-pharmacology-screening-data-import.component.ts @@ -84,7 +84,7 @@ export class InvitroPharmacologyScreeningDataImportComponent implements OnInit { submitMessage = ''; resultMessage = ''; disableValidateButton = "true"; - disableImportButton = "false"; + disableImportButton = "true"; isExcelDataLoaded = false; canUpdate: boolean = false; @@ -656,7 +656,7 @@ export class InvitroPharmacologyScreeningDataImportComponent implements OnInit { let testDateNum = this.replaceUndefinedValue(element["Test Date (mm/dd/yyyy)"]); element["testAgentConcentration"] = this.replaceUndefinedValue(element["Test Agent Concentration"]); element["testAgentConcentrationUnits"] = this.replaceUndefinedValue(element["Test Agent Concentration Units"]); - element["resultValue"] = this.replaceUndefinedValue(element["Result Value"]); + element["resultValue"] = this.replaceUndefinedValue(element["Result Value (Average)"]); element["resultValueUnits"] = this.replaceUndefinedValue(element["Result Value Units"]); element["ligandSubstrateConcentration"] = this.replaceUndefinedValue(element["Ligand/Substrate Concentration"]); element["ligandSubstrateConcentrationUnits"] = this.replaceUndefinedValue(element["Ligand/Substrate Concentration Units"]); @@ -707,7 +707,8 @@ export class InvitroPharmacologyScreeningDataImportComponent implements OnInit { delete element["Test Date (mm/dd/yyyy)"]; delete element["Test Agent Concentration"]; delete element["Test Agent Concentration Units"]; - delete element["Result Value"]; + delete element["Result Value (Average)"]; + delete element["Result Value IC50"]; delete element["Result Value Units"]; delete element["Ligand/Substrate Concentration"]; delete element["Ligand/Substrate Concentration Units"]; @@ -976,6 +977,7 @@ export class InvitroPharmacologyScreeningDataImportComponent implements OnInit { validate() { this.assayToSaveAllList = []; this.assayToSaveList = []; + this.firstAssayRemainingScreening = []; this.savedResultInfo = null; this.resultMessage = 'Checking Assays in the database...'; @@ -1061,6 +1063,10 @@ export class InvitroPharmacologyScreeningDataImportComponent implements OnInit { } importAssayJSONIntoDatabase() { + const dialogRef = this.dialog.open(this.progressDialogTemplateRef, { + width: '500px', + disableClose: false // Prevent user from closing it while the process is running,if set to true + }); this.combineAssaysAndScreenings(); @@ -1068,7 +1074,7 @@ export class InvitroPharmacologyScreeningDataImportComponent implements OnInit { } importFirstScreeningResult() { - + this.progressMessage = 'Preparing to save assays...'; let assayApiUrlList: any = []; const params = new HttpParams(); @@ -1135,27 +1141,38 @@ export class InvitroPharmacologyScreeningDataImportComponent implements OnInit { // return list of array of the result resultList.forEach((result) => { if (result.id) { + + this.progressMessage = "Saved 1 of " + this.assayToSaveList.length; + savedCount = savedCount + 1; if (result.invitroAssayScreenings && result.invitroAssayScreenings.length > 0) { if (result.invitroAssayScreenings[result.invitroAssayScreenings.length - 1].invitroAssayResultInformation) { if (result.invitroAssayScreenings[result.invitroAssayScreenings.length - 1].invitroAssayResultInformation.id) { this.savedResultInfo = result.invitroAssayScreenings[result.invitroAssayScreenings.length - 1].invitroAssayResultInformation; - - + let newAssay = _.clone(result); - - if (this.firstAssayRemainingScreening != null && this.firstAssayRemainingScreening.length > 0) { - this.firstAssayRemainingScreening.forEach(screening => { - if (screening) { - screening.invitroAssayResultInformation = this.savedResultInfo; - newAssay.invitroAssayScreenings.push(screening); + if (this.firstAssayRemainingScreening != null && this.firstAssayRemainingScreening.length > 0) { + this.firstAssayRemainingScreening.forEach(screening => { + if (screening) { + if (!screening.invitroAssayResultInformation || !screening.invitroAssayResultInformation.id) { + screening.invitroAssayResultInformation = JSON.parse(JSON.stringify(this.savedResultInfo)); } - }); - - this.assayToSaveList[0] = newAssay; - } + newAssay.invitroAssayScreenings.push(screening); + } + }); + + this.assayToSaveList[0] = newAssay; + + } else { + // remove the first Assay from the list + // Only one screening in First Assay, so remove it from the lists + // Removes first item + if (this.assayToSaveList && this.assayToSaveList.length > 0) { + this.assayToSaveList.shift(); + } + } } } } @@ -1196,57 +1213,33 @@ export class InvitroPharmacologyScreeningDataImportComponent implements OnInit { }; // This will hold the result information from the first saved assay for final navigation. - // Initialize state and open the dialog immediately --- - this.savedCount = 0; - this.totalAssays = this.assayToSaveList.length; - this.progressMessage = 'Preparing to save assays...'; this.isComplete = false; this.isError = false; let assayReadyToSave: InvitroAssayInformation; - const dialogRef = this.dialog.open(this.progressDialogTemplateRef, { - width: '500px', - disableClose: true // Prevent user from closing it while the process is running. - }); - - // --- B. Start the sequential save stream using RxJS --- + // Start the sequential save stream using RxJS --- from(this.assayToSaveList).pipe( // 'concatMap' ensures each assay is processed one by one, waiting for the previous save to complete. concatMap((assayToSave, index) => { - // Update the dialog message before each save attempt. - this.progressMessage = `Saving assay ${index + 1} of ${this.totalAssays}...`; assayReadyToSave = _.cloneDeep(assayToSave); - // } - - // Check if index is 0 - const isFirstAssay = (index === 0); - // Prepare the payload based on whether it's the first assay or a subsequent one. - if (isFirstAssay) { - - const indexToKeep = assayReadyToSave.invitroAssayScreenings.findIndex(screening => !screening.id); - - // Check if a screening to keep was found. - if (indexToKeep > -1) { - // Keep the first screening without an ID, and remove the rest of screening that do not have Ids. - assayReadyToSave.invitroAssayScreenings.splice(indexToKeep, 1); - } - - } - - // Assign savedResultInfo + /* assayReadyToSave.invitroAssayScreenings.forEach((screening, indexScreening) => { if (screening) { - if (screening.invitroAssayResultInformation == null) { - screening.invitroAssayResultInformation = {}; - screening.invitroAssayResultInformation = this.savedResultInfo; - } - if (screening.invitroAssayResultInformation && !screening.invitroAssayResultInformation.id) { + if (!screening.id) { + alert("SCREENING ID " + screening.id); + if (!screening.invitroAssayResultInformation || !screening.invitroAssayResultInformation.id) { + alert("AAAAAAAAAAAAAAAAAaa"); + alert("AAAAAAAAA INSIDE SETTING REFINFO " + screening.id + " " + screening.invitroAssayResultInformation.id); + // screening.invitroAssayResultInformation = this.savedResultInfo; + } else { + alert("GGGGGGGGGGGGGGGGGGGG " + screening.invitroAssayResultInformation + " " + screening.invitroAssayResultInformation.id); + } } } - }); + });*/ // Set the assay on the service (following your existing stateful pattern). this.invitroPharmacologyService.assay = assayReadyToSave; @@ -1261,8 +1254,11 @@ export class InvitroPharmacologyScreeningDataImportComponent implements OnInit { initialState.lastCheckedAssayId = savedAssay.externalAssayId; // ADDED: Update the ID in our state initialState.cachedAssay = _.cloneDeep(savedAssay); // Cache the new assay - this.savedCount = index + 1; // Update the count for the dialog. - + let savedAssayCount = index + 1; + if (this.firstAssayRemainingScreening && this.firstAssayRemainingScreening.length > 0) { + savedAssayCount = index + 2; + } + this.progressMessage = "Saved " + savedAssayCount + " of " + this.assayToSaveList.length; }) ); // return }) @@ -1280,16 +1276,16 @@ export class InvitroPharmacologyScreeningDataImportComponent implements OnInit { // --- D. Handle successful completion of the entire stream --- complete: () => { this.isComplete = true; - this.progressMessage = 'All assays have been imported successfully!'; + this.progressMessage = 'All Assays have been imported successfully!'; + this.close(); // Wait for the user to close the completed dialog before navigating. - dialogRef.afterClosed().subscribe(() => { - console.log('Navigating to edit page...'); - this.invitroPharmacologyService.bypassUpdateCheck(); - this.router.routeReuseStrategy.shouldReuseRoute = () => false; - this.router.onSameUrlNavigation = 'reload'; - this.router.navigate(['/invitro-pharm/', this.savedResultInfo.id, 'edit']); - }); + // dialogRef.afterClosed().subscribe(() => { + this.invitroPharmacologyService.bypassUpdateCheck(); + this.router.routeReuseStrategy.shouldReuseRoute = () => false; + this.router.onSameUrlNavigation = 'reload'; + this.router.navigate(['/invitro-pharm/', this.savedResultInfo.id, 'edit']); + // }); } }); } @@ -1316,6 +1312,10 @@ export class InvitroPharmacologyScreeningDataImportComponent implements OnInit { this.subscriptions.push(dialogSubscription); } + close() { + this.dialog.closeAll(); + } + isNumber(str: any): boolean { if (str) { const num = Number(str); From 9329feb01cab3cf310c29d41aaf84eac62f2bf77 Mon Sep 17 00:00:00 2001 From: Newatia Date: Tue, 7 Apr 2026 12:36:08 -0400 Subject: [PATCH 354/408] updated IVP --- ...harmacology-assay-data-import.component.ts | 2 - .../invitro-pharmacology-form.component.html | 48 +++++++------- .../invitro-pharmacology-form.component.scss | 65 ++++++++++++------- ...acology-screening-data-import.component.ts | 17 +++-- 4 files changed, 74 insertions(+), 58 deletions(-) diff --git a/src/app/fda/invitro-pharmacology/invitro-pharmacology-assay-data-import/invitro-pharmacology-assay-data-import.component.ts b/src/app/fda/invitro-pharmacology/invitro-pharmacology-assay-data-import/invitro-pharmacology-assay-data-import.component.ts index afd61ed1b..63ac2e2eb 100644 --- a/src/app/fda/invitro-pharmacology/invitro-pharmacology-assay-data-import/invitro-pharmacology-assay-data-import.component.ts +++ b/src/app/fda/invitro-pharmacology/invitro-pharmacology-assay-data-import/invitro-pharmacology-assay-data-import.component.ts @@ -627,7 +627,6 @@ export class InvitroPharmacologyAssayDataImportComponent implements OnInit { savedCompleted(savedCount, AssayList) { // All records saved - console.log("AAAAAAA COMPLETE" + savedCount + " " + AssayList.length); if (savedCount == AssayList.length) { this.message = ""; this.submitMessage = "Import Successful"; @@ -707,7 +706,6 @@ export class InvitroPharmacologyAssayDataImportComponent implements OnInit { } }, error => { // Error occured during saving - console.log("ERROR DURING SAVING ASSAY IMPORT " + error) const saved = { 'indexRecord': index, 'assayId': element['assayId'], 'externalAssaySource': element['externalAssaySource'], 'externalAssayId': element['externalAssayId'], 'saved': 'No', 'savedId': '', 'error': error } this.importSaveMessageArray.push(saved); diff --git a/src/app/fda/invitro-pharmacology/invitro-pharmacology-form/invitro-pharmacology-form.component.html b/src/app/fda/invitro-pharmacology/invitro-pharmacology-form/invitro-pharmacology-form.component.html index d9162901b..d8bc8f62b 100644 --- a/src/app/fda/invitro-pharmacology/invitro-pharmacology-form/invitro-pharmacology-form.component.html +++ b/src/app/fda/invitro-pharmacology/invitro-pharmacology-form/invitro-pharmacology-form.component.html @@ -314,7 +314,8 @@ Laboratory Affiliation:
    -
    @@ -324,7 +325,8 @@ Laboratory Street Address:
    -
    @@ -1152,8 +1154,8 @@
    - Test Agent Concentration + @@ -1164,8 +1166,8 @@ - + Result Value + - Ligand/Substrate Concentration + @@ -1245,13 +1247,13 @@ - + Protein + - Plasma Protein Concentration + @@ -1271,8 +1273,8 @@ --> - + Test Date +
    - - + Measurements +
    @@ -1356,8 +1359,8 @@ - + Control Substance Approval ID + - + Control Reference Value + { +.mat-form-field-style> { /* OVERWRITE MATERIAL INPUT FIELDS */ .mat-form-field-infix { @@ -705,12 +712,12 @@ hr { /*Focused: change color of underline*/ .mat-form-field-ripple { - background-color: var(--mat-form-field-focused-color) !important;; + background-color: var(--mat-form-field-focused-color) !important; + ; } - .mat-form-field-disabled .mat-form-field-underline - { - background-image: linear-gradient( to right, var(--img-linear-gradient-start-color) 0, var(--textarea-dark-border-color) 10%, var(--img-linear-gradient-color) 0 ) !important; + .mat-form-field-disabled .mat-form-field-underline { + background-image: linear-gradient(to right, var(--img-linear-gradient-start-color) 0, var(--textarea-dark-border-color) 10%, var(--img-linear-gradient-color) 0) !important; background-size: 1px 100% !important; background-repeat: repeat-x !important; cursor: not-allowed; @@ -725,9 +732,12 @@ hr { } } -.mat-expansion-indicator -{ - pointer-events: visiblefill !important; +.mat-expansion-indicator { + pointer-events: visiblefill !important; +} + +::ng-deep .mat-mdc-text-field-wrapper { + flex: unset; } .panel-style { @@ -764,7 +774,8 @@ fieldset.border { margin-bottom: 50px; border-bottom: none; border-radius: 8px; - min-width: 0; /* override the default value of min-content */ + min-width: 0; + /* override the default value of min-content */ -webkit-box-shadow: 3px 6px 11px 1px var(--fieldset-box-shadow-color); -moz-box-shadow: 3px 6px 11px 1px var(--fieldset-box-shadow-color); box-shadow: 3px 4px 5px 1px var(--fieldset-box-shadow-color); @@ -786,6 +797,7 @@ legend.border { background: var(--mustard-color); color: var(--regular-white-color); } + .mat-badge-medium .mat-badge-above .mat-badge-top .mat-badge-content { top: unset; right: unset; @@ -827,8 +839,8 @@ legend.border { text-transform: uppercase; font-weight: 500; margin-right: 20px; - padding:10px; - border-radius:3px; + padding: 10px; + border-radius: 3px; } } @@ -859,7 +871,8 @@ table.tableStyle { border-collapse: collapse; } -table.tableStyle td, table.tableStyle th { +table.tableStyle td, +table.tableStyle th { border: 1px solid var(--table-th-border-color); /*padding: 3px 2px;*/ } @@ -878,7 +891,8 @@ table.tableStyle tr:nth-child(even) { } table.tableStyle thead { - background: var(--table-thead-bg-color); /* Header background */ + background: var(--table-thead-bg-color); + /* Header background */ border-bottom: 1px solid var(--table-thead-border-color); } @@ -911,11 +925,12 @@ table.tableStyle tfoot .links { text-align: right; } -table.tableStyle tfoot .links a{ +table.tableStyle tfoot .links a { display: inline-block; background: var(--secondary-blue-color); color: var(--white-color); padding: 2px 8px; border-radius: 5px; } + /* Table Style End */ \ No newline at end of file diff --git a/src/app/fda/invitro-pharmacology/invitro-pharmacology-screening-data-import/invitro-pharmacology-screening-data-import.component.ts b/src/app/fda/invitro-pharmacology/invitro-pharmacology-screening-data-import/invitro-pharmacology-screening-data-import.component.ts index 1cbf6c38b..dd26900ed 100644 --- a/src/app/fda/invitro-pharmacology/invitro-pharmacology-screening-data-import/invitro-pharmacology-screening-data-import.component.ts +++ b/src/app/fda/invitro-pharmacology/invitro-pharmacology-screening-data-import/invitro-pharmacology-screening-data-import.component.ts @@ -1225,21 +1225,15 @@ export class InvitroPharmacologyScreeningDataImportComponent implements OnInit { assayReadyToSave = _.cloneDeep(assayToSave); - /* assayReadyToSave.invitroAssayScreenings.forEach((screening, indexScreening) => { if (screening) { if (!screening.id) { - alert("SCREENING ID " + screening.id); if (!screening.invitroAssayResultInformation || !screening.invitroAssayResultInformation.id) { - alert("AAAAAAAAAAAAAAAAAaa"); - alert("AAAAAAAAA INSIDE SETTING REFINFO " + screening.id + " " + screening.invitroAssayResultInformation.id); - // screening.invitroAssayResultInformation = this.savedResultInfo; - } else { - alert("GGGGGGGGGGGGGGGGGGGG " + screening.invitroAssayResultInformation + " " + screening.invitroAssayResultInformation.id); + screening.invitroAssayResultInformation = this.savedResultInfo; } } } - });*/ + }); // Set the assay on the service (following your existing stateful pattern). this.invitroPharmacologyService.assay = assayReadyToSave; @@ -1264,7 +1258,12 @@ export class InvitroPharmacologyScreeningDataImportComponent implements OnInit { }) ).subscribe({ // 'next' is handled by tap, so this can be empty. It's called after each successful save. - next: () => { }, + next: (savedAssay) => { + + if (savedAssay) { + this.savedResultInfo = savedAssay.invitroAssayScreenings[savedAssay.invitroAssayScreenings.length - 1].invitroAssayResultInformation; + } + }, // --- C. Handle any error in the stream --- error: err => { From 143102b43c562b17925e73b74eb81c97ce1e0b4c Mon Sep 17 00:00:00 2001 From: Iaroslav Moskviak Date: Wed, 8 Apr 2026 11:08:28 -0400 Subject: [PATCH 355/408] user roles fix and add to list dialog ui fix --- .../user-edit-dialog.component.ts | 2 +- .../list-create-dialog.component.html | 32 +++++--- .../list-create-dialog.component.scss | 9 +++ src/app/fda/config/config.json | 73 ++++++++----------- src/styles/_material-overrides.scss | 3 + 5 files changed, 62 insertions(+), 57 deletions(-) diff --git a/src/app/core/admin/user-management/user-edit-dialog/user-edit-dialog.component.ts b/src/app/core/admin/user-management/user-edit-dialog/user-edit-dialog.component.ts index df855f3b7..1ed593e71 100644 --- a/src/app/core/admin/user-management/user-edit-dialog/user-edit-dialog.component.ts +++ b/src/app/core/admin/user-management/user-edit-dialog/user-edit-dialog.component.ts @@ -360,7 +360,7 @@ export class UserEditDialogComponent implements OnInit { } private getRoleNumericValue(roleName: string): number { - if(!roleName || roleName === null || roleName.length ==- 0 ) return + if(!roleName || roleName === null || roleName.length === 0 ) return (this.configService.configData.roleSortingConfig && this.configService.configData.roleSortingConfig["null"] != null ) ? this.configService.configData.roleSortingConfig["null"] : 0; diff --git a/src/app/core/substances-browse/list-create-dialog/list-create-dialog.component.html b/src/app/core/substances-browse/list-create-dialog/list-create-dialog.component.html index c00c9d0a6..99dc153d5 100644 --- a/src/app/core/substances-browse/list-create-dialog/list-create-dialog.component.html +++ b/src/app/core/substances-browse/list-create-dialog/list-create-dialog.component.html @@ -1,13 +1,21 @@ -

    Create List from {{record._name}}

    +

    Create List from {{ record._name }}

    -
    -
    -     - -
    -{{message}} -
    -
    - - -
    \ No newline at end of file + +
    + +     + +
    + {{ message }} +
    + + + + diff --git a/src/app/core/substances-browse/list-create-dialog/list-create-dialog.component.scss b/src/app/core/substances-browse/list-create-dialog/list-create-dialog.component.scss index 8bcd1671b..8715e7f9a 100644 --- a/src/app/core/substances-browse/list-create-dialog/list-create-dialog.component.scss +++ b/src/app/core/substances-browse/list-create-dialog/list-create-dialog.component.scss @@ -1,3 +1,12 @@ .list-dialog { padding: 10px; + margin-top: 39px; +} + +h2 { + padding: 0; +} + +.actions { + padding: 0 !important; } diff --git a/src/app/fda/config/config.json b/src/app/fda/config/config.json index 171276d6f..f2566f50e 100644 --- a/src/app/fda/config/config.json +++ b/src/app/fda/config/config.json @@ -19,8 +19,8 @@ "polymerDisclaimer": "Please do not consider GSRS to be an expert system when registering polymer substances.", "disableJSDraw": false, "registerApplicationCenterNotAllowed": ["CDER", "CBER"], - "apiBaseUrl": "http://localhost:8080", - "formerApiBaseUrl" : "http://localhost:8081/ginas/app/", + "apiBaseUrl": "http://localhost:8080/", + "formerApiBaseUrl": "http://localhost:8081/ginas/app/", "gsrsHomeBaseUrl": "http://localhost:8081/ginas/app/ui/", "apiSSG4mBaseUrl": "http://localhost:8081/ginas/app/", "occasionalApiBasePath": "/ginas/app", @@ -46,42 +46,39 @@ } } }, - "enablePDFDownload":{ - "enablePDFDownload" : true, - "buttonName":"Print to PDF", - "companyName" : "", - "proprietaryNote":"" + "enablePDFDownload": { + "enablePDFDownload": true, + "buttonName": "Print to PDF", + "companyName": "", + "proprietaryNote": "" }, "elementLabelDisplay": { "labels": { "substance_names_name": { - "displayNameTitle": "Display Name", - "displayNameShortTitle":"DN", - "preferredTitle": "Additional Listing Name", - "preferredShortTitle": "AL" + "displayNameTitle": "Display Name", + "displayNameShortTitle": "DN", + "preferredTitle": "Additional Listing Name", + "preferredShortTitle": "AL" } } }, "bulkSearch": { "entities": [ { - "name":"substances", + "name": "substances", "title": "Substances" }, { - "name":"products", + "name": "products", "title": "Products" }, { - "name":"applications", + "name": "applications", "title": "Applications" } ] }, - "filteredDuplicationCodes": [ - "BDNUM", - "FDA UNII" - ], + "filteredDuplicationCodes": ["BDNUM", "FDA UNII"], "typeaheadFields": [ "Standardized_Name", "Display_Name", @@ -108,11 +105,11 @@ { "name": "frontend", "active": true, "hasEntities": false }, { "name": "gateway", "active": true, "hasEntities": false }, { "name": "impurities", "active": true, "hasEntities": true }, - { "name": "invitro-pharmacology", "active":true, "hasEntities": true }, + { "name": "invitro-pharmacology", "active": true, "hasEntities": true }, { "name": "products", "active": true, "hasEntities": true }, { "name": "ssg4m", "active": true, "hasEntities": true }, { "name": "substances", "active": true, "hasEntities": true } - ], + ], "usefulLinks": [ { "title": "GSRSFind Excel tools", @@ -160,9 +157,7 @@ { "card": "fda-substance-product", "title": "Products, Applications, Clinical Trials, Adverse Events, Impurities Specs, SSG4 Manufacturing, In Vitro Pharmacology", - "filters": [ - {} - ] + "filters": [{}] }, { "card": "substance-primary-definition", @@ -799,11 +794,7 @@ }, { "category": "User Data", - "facets": [ - "Record Created By", - "Approved By", - "root_lastEditedBy" - ] + "facets": ["Record Created By", "Approved By", "root_lastEditedBy"] }, { "category": "CMC Data", @@ -1192,13 +1183,7 @@ ] } }, - "codeSystemOrder": [ - "BDNUM", - "CAS", - "WHO-ATC", - "EVMPD", - "NCI" - ], + "codeSystemOrder": ["BDNUM", "CAS", "WHO-ATC", "EVMPD", "NCI"], "homeHeader": "Global Substance Registration System - GSRS", "homeContents": "

    The primary goal of the Global Substance Registration System (GSRS) program is the production of software to assist agencies in registering and documenting information about substances found in medicines and other regulated products. GSRS provides common identifiers including FDA UNIIs for all substances used in regulated products. It utilizes definitions of substances globally-consistent with ISO 11238 and DTS 19844 including active substances under clinical investigation.\n\n

    \n\n      \n\n\n\n

    Many organizations contribute to the GSRS project including the Food and Drug Administration (FDA) and the National Center for Advancing Translational Science (NCATS). For more information about GSRS, code and public data, please consult https://gsrs.ncats.nih.gov/.

    \n\n

    ", "relationshipsVisualizationUri": "/ginas/app/beta/substanceRelationshipVisualizer/index.html?uuid=", @@ -1357,7 +1342,7 @@ "kind": "contact-us", "display": "Email GSRS Support", "mailToPath": "mailto:%s", - "queryParams": {"subject" : "Support request"}, + "queryParams": { "subject": "Support request" }, "order": 30 } ] @@ -1367,7 +1352,7 @@ "kind": "contact-us", "display": "Contact Us", "mailToPath": "mailto:%s", - "queryParams": {"subject" : "Support request"}, + "queryParams": { "subject": "Support request" }, "order": 2000 } ], @@ -1379,13 +1364,13 @@ "root_codes_CAS", "root_codes_ECHA" ], - "roleSortConfig" : [ - {"roleName" : "null", "numericValue": 0}, - {"roleName" : "ADMIN", "numericValue": 100}, - {"roleName" : "APPROVER", "numericValue": 80}, - {"roleName" : "DATAENTRY", "numericValue": 50}, - {"roleName" : "QUERY", "numericValue": 10} - ], + "roleSortingConfig": { + "NULL": 0, + "ADMIN": 100, + "APPROVER": 80, + "DATAENTRY": 50, + "QUERY": 10 + }, "homeDynamicLinks": [ { "display": "Chemicals", diff --git a/src/styles/_material-overrides.scss b/src/styles/_material-overrides.scss index 08cfe370a..ce80c078d 100644 --- a/src/styles/_material-overrides.scss +++ b/src/styles/_material-overrides.scss @@ -821,6 +821,8 @@ button.mat-mdc-menu-item[ng-reflect-menu], .mat-mdc-dialog-container { --mat-dialog-supporting-text-color: black; + --mat-dialog-container-max-width: none; + max-width: none !important; .mdc-dialog__surface { border-radius: 4px; @@ -883,6 +885,7 @@ button.mat-mdc-menu-item[ng-reflect-menu], .mat-mdc-dialog-inner-container { height: fit-content !important; max-height: 90vh !important; + max-width: none !important; overflow-y: auto !important; } From 7c860a2e83b390fa7f0a167c5af036bc52c93d35 Mon Sep 17 00:00:00 2001 From: Mitch Miller Date: Thu, 9 Apr 2026 15:23:58 -0400 Subject: [PATCH 356/408] restoring the restriction on the Record History card. --- .../substance-cards-filters.constant.ts | 29 +++++++------------ 1 file changed, 11 insertions(+), 18 deletions(-) diff --git a/src/app/core/substance-details/substance-cards-filters.constant.ts b/src/app/core/substance-details/substance-cards-filters.constant.ts index e997cdc73..3da7236f6 100644 --- a/src/app/core/substance-details/substance-cards-filters.constant.ts +++ b/src/app/core/substance-details/substance-cards-filters.constant.ts @@ -2,7 +2,8 @@ import { SubstanceCardFilter } from './substance-cards-filter.model'; import { SubstanceDetail } from '../substance/substance.model'; import { SubstanceCardFilterParameters } from '../config/config.model'; import { getEvaluatedProperty } from './substance-cards-utils'; -import { Observable } from 'rxjs'; +import { of, from, Observable } from 'rxjs'; +import { map } from 'rxjs/operators'; import { AuthService} from '@gsrs-core/auth/auth.service'; import {HttpClient} from '@angular/common/http'; @@ -213,25 +214,18 @@ export function substanceRelationshipsFilter( }); } - export function credentialsFilter( - substance: SubstanceDetail, - filter: SubstanceCardFilterParameters, - http: HttpClient, - auth: AuthService - ): Observable { - return new Observable(observer => { +export function credentialsFilter(substance: SubstanceDetail, + filter: SubstanceCardFilterParameters, + http: HttpClient, + auth: AuthService): Observable { + if (!filter.propertyToCheck) return of(false); - let isApproved = false; - if (filter.propertyToCheck != null) { - if (auth.hasSpecificPrivilege(filter.propertyToCheck)) { - isApproved = true; - } - } - observer.next(isApproved); - observer.complete(); - }); + return from(Promise.resolve(auth.hasSpecificPrivilege(filter.propertyToCheck))).pipe( + map((r: boolean) => !!r) + ); } + export function groupFilter( substance: SubstanceDetail, filter: SubstanceCardFilterParameters, @@ -250,4 +244,3 @@ export function groupFilter( observer.complete(); }); } - From e5056b2234c28eceaef5bd438260c4c6cd508f4e Mon Sep 17 00:00:00 2001 From: Iaroslav Moskviak Date: Thu, 9 Apr 2026 16:53:28 -0400 Subject: [PATCH 357/408] admin facets fix --- .../user-edit-dialog.component.html | 26 +++++++++---------- .../facets-manager.component.ts | 14 ++++++++-- 2 files changed, 25 insertions(+), 15 deletions(-) diff --git a/src/app/core/admin/user-management/user-edit-dialog/user-edit-dialog.component.html b/src/app/core/admin/user-management/user-edit-dialog/user-edit-dialog.component.html index e32a9d7d4..be9952710 100644 --- a/src/app/core/admin/user-management/user-edit-dialog/user-edit-dialog.component.html +++ b/src/app/core/admin/user-management/user-edit-dialog/user-edit-dialog.component.html @@ -1,5 +1,7 @@

    {{ message }}
    -

    {{ newUser ? 'Add User' : 'Edit User' }}

    +

    + {{ newUser ? "Add User" : "Edit User" }} +

    @@ -8,7 +10,6 @@

    {{ newUser ? 'Add User' : 'Edit User' }} Username {{ newUser ? 'Add User' : 'Edit User' }}
    @@ -44,11 +40,11 @@

    {{ newUser ? 'Add User' : 'Edit User' }}
    + new password @@ -56,11 +52,11 @@

    {{ newUser ? 'Add User' : 'Edit User' }}

    + confirm new password @@ -81,11 +77,11 @@

    {{ newUser ? 'Add User' : 'Edit User' }}
    + new password @@ -93,11 +89,11 @@

    {{ newUser ? 'Add User' : 'Edit User' }}

    + confirm new password @@ -126,7 +122,11 @@

    {{ newUser ? 'Add User' : 'Edit User' }}
    Roles - + {{ role.roleName }} diff --git a/src/app/core/facets-manager/facets-manager.component.ts b/src/app/core/facets-manager/facets-manager.component.ts index f91c8b978..71c4017f8 100644 --- a/src/app/core/facets-manager/facets-manager.component.ts +++ b/src/app/core/facets-manager/facets-manager.component.ts @@ -297,7 +297,8 @@ export class FacetsManagerComponent implements OnInit, OnDestroy, AfterViewInit this.facetsAuthSubscription.unsubscribe(); this.facetsAuthSubscription = null; } - this.facetsAuthSubscription = this.authService.getAuth().subscribe(auth => { + this.facetsAuthSubscription = this.authService.getAuth().subscribe(async auth => { + const isAdmin = auth ? await this.authService.hasSpecificPrivilege('Configure System') : false; const facetsCopy = this.privateRawFacets.slice(); const newFacets = []; let facetKeys = Object.keys(this.facetsConfig) || []; @@ -305,7 +306,10 @@ export class FacetsManagerComponent implements OnInit, OnDestroy, AfterViewInit if (this._facetDisplayType === 'default' || this.calledFrom === 'staging') { facetKeys.forEach(facetKey => { if (this.facetsConfig[facetKey].length - && (facetKey === 'default' || this.authService.hasRoles(facetKey) || (facetKey === 'staging' && this.calledFrom === 'staging'))) { + && (facetKey === 'default' + || (facetKey === 'admin' && isAdmin) + || (facetKey !== 'admin' && this.authService.hasRoles(facetKey)) + || (facetKey === 'staging' && this.calledFrom === 'staging'))) { this.facetsConfig[facetKey].forEach(facet => { for (let facetIndex = 0; facetIndex < facetsCopy.length; facetIndex++) { this.toggle[facetIndex] = true; @@ -404,6 +408,12 @@ export class FacetsManagerComponent implements OnInit, OnDestroy, AfterViewInit } } + // Filter out admin-only facets for non-admin users + if (this.facetsConfig['admin'] && !isAdmin) { + const adminFacetNames = new Set(this.facetsConfig['admin']); + newFacets.splice(0, newFacets.length, ...newFacets.filter(f => !adminFacetNames.has(f.name))); + } + // Set any facets being used to filter results to the top of the facet display Object.keys(this.privateFacetParams).forEach(key => { const position = newFacets.map(object => object.name).indexOf(key); From 9d732fa6abf94d62a027a63f17b559ae75890298 Mon Sep 17 00:00:00 2001 From: Mitch Miller Date: Thu, 9 Apr 2026 22:19:56 -0400 Subject: [PATCH 358/408] make search menu items available to query users --- src/app/core/base/base.component.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/app/core/base/base.component.ts b/src/app/core/base/base.component.ts index 1e4b51bd7..59eed76a2 100644 --- a/src/app/core/base/base.component.ts +++ b/src/app/core/base/base.component.ts @@ -207,9 +207,9 @@ export class BaseComponent implements OnInit, OnDestroy { this.versionTooltipMessage += ` built on ${moment(new Date(buildInfo.buildTime)).utc().format("ddd MMM D YYYY HH:mm:ss z")}`; }); let okToRegister: boolean = await this.authService.canEditData(); - if (okToRegister) { + this.navItems.forEach((item) => { - if (item.display === "Register") { + if (item.display === "Register" && okToRegister) { this.registerNav = item.children; } if (item.display === "Search") { @@ -236,7 +236,7 @@ export class BaseComponent implements OnInit, OnDestroy { } } } - } + this.overlayContainer = this.overlayContainerService.getContainerElement(); let urlPath = this.router.routerState.snapshot.url.split("?")[0]; From efc325487916c98c6f479ea6e5b8f1486235c39d Mon Sep 17 00:00:00 2001 From: alx652 <58182629+alx652@users.noreply.github.com> Date: Fri, 10 Apr 2026 09:29:37 -0400 Subject: [PATCH 359/408] possible mistaken commit --- src/app/fda/config/config.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/app/fda/config/config.json b/src/app/fda/config/config.json index f2566f50e..021bd58a4 100644 --- a/src/app/fda/config/config.json +++ b/src/app/fda/config/config.json @@ -19,8 +19,7 @@ "polymerDisclaimer": "Please do not consider GSRS to be an expert system when registering polymer substances.", "disableJSDraw": false, "registerApplicationCenterNotAllowed": ["CDER", "CBER"], - "apiBaseUrl": "http://localhost:8080/", - "formerApiBaseUrl": "http://localhost:8081/ginas/app/", + "apiBaseUrl": "http://localhost:8081/ginas/app/", "gsrsHomeBaseUrl": "http://localhost:8081/ginas/app/ui/", "apiSSG4mBaseUrl": "http://localhost:8081/ginas/app/", "occasionalApiBasePath": "/ginas/app", From 5e9f78fdee1d7fe8c028337c9f4847285e61e188 Mon Sep 17 00:00:00 2001 From: Mitch Miller Date: Fri, 10 Apr 2026 11:48:05 -0400 Subject: [PATCH 360/408] cleaned up formatting --- src/app/core/base/base.component.ts | 50 +++++++++++++---------------- 1 file changed, 23 insertions(+), 27 deletions(-) diff --git a/src/app/core/base/base.component.ts b/src/app/core/base/base.component.ts index 59eed76a2..ff31e2599 100644 --- a/src/app/core/base/base.component.ts +++ b/src/app/core/base/base.component.ts @@ -95,8 +95,7 @@ export class BaseComponent implements OnInit, OnDestroy { private utilsService: UtilsService, private wildCardService: WildcardService, ) { - this.customToolbarComponent = - this.configService.configData.customToolbarComponent; + this.customToolbarComponent = this.configService.configData.customToolbarComponent; this.wildCardService.wildCardObservable.subscribe((data) => { this.wildCardText = data; }); @@ -111,8 +110,7 @@ export class BaseComponent implements OnInit, OnDestroy { let range: Range; let selectionStart: number; let selectionEnd: number; - const activeEl: HTMLInputElement = - document.activeElement as HTMLInputElement; + const activeEl: HTMLInputElement = document.activeElement as HTMLInputElement; if (activeEl != null) { const activeElTagName = activeEl ? activeEl.tagName.toLowerCase() : null; @@ -143,10 +141,8 @@ export class BaseComponent implements OnInit, OnDestroy { } async ngOnInit() { - this.showHeaderBar = - this.activatedRoute.snapshot.queryParams["header"] || "true"; - this.loadedComponents = - this.configService.configData.loadedComponents || null; + this.showHeaderBar = this.activatedRoute.snapshot.queryParams["header"] || "true"; + this.loadedComponents = this.configService.configData.loadedComponents || null; this.classicLinkPath = this.configService.environment.clasicBaseHref; this.clasicBaseHref = this.configService.environment.clasicBaseHref; @@ -208,34 +204,34 @@ export class BaseComponent implements OnInit, OnDestroy { }); let okToRegister: boolean = await this.authService.canEditData(); - this.navItems.forEach((item) => { - if (item.display === "Register" && okToRegister) { - this.registerNav = item.children; - } - if (item.display === "Search") { - this.searchNav = item.children; - } - }); - if (this.loadedComponents) { - for (let i = this.navItems.length - 1; i >= 0; i--) { - if (this.navItems[i].children) { - for (let j = this.navItems[i].children.length - 1; j >= 0; j--) { - if (this.navItems[i].children[j].component) { - if ( - !this.loadedComponents[this.navItems[i].children[j].component] - ) { - this.navItems[i].children.splice(j, 1); - } + this.navItems.forEach((item) => { + if (item.display === "Register" && okToRegister) { + this.registerNav = item.children; + } + if (item.display === "Search") { + this.searchNav = item.children; + } + }); + if (this.loadedComponents) { + for (let i = this.navItems.length - 1; i >= 0; i--) { + if (this.navItems[i].children) { + for (let j = this.navItems[i].children.length - 1; j >= 0; j--) { + if (this.navItems[i].children[j].component) { + if ( + !this.loadedComponents[this.navItems[i].children[j].component] + ) { + this.navItems[i].children.splice(j, 1); + } } } } if (this.navItems[i].component) { if (!this.loadedComponents[this.navItems[i].component]) { this.navItems.splice(i, 1); - } } } } + } this.overlayContainer = this.overlayContainerService.getContainerElement(); From cd9e182affd49f265d73c19813d4f43477c1c1e1 Mon Sep 17 00:00:00 2001 From: Iaroslav Moskviak Date: Fri, 10 Apr 2026 12:56:42 -0400 Subject: [PATCH 361/408] simlified chemical Name textarea fix --- src/app/core/base/base.component.ts | 8 ++++---- .../simplified-names/simplified-name-form.component.html | 1 + .../simplified-names/simplified-name-form.component.scss | 3 --- 3 files changed, 5 insertions(+), 7 deletions(-) diff --git a/src/app/core/base/base.component.ts b/src/app/core/base/base.component.ts index ff31e2599..a3fda4181 100644 --- a/src/app/core/base/base.component.ts +++ b/src/app/core/base/base.component.ts @@ -222,12 +222,12 @@ export class BaseComponent implements OnInit, OnDestroy { ) { this.navItems[i].children.splice(j, 1); } - } } } - if (this.navItems[i].component) { - if (!this.loadedComponents[this.navItems[i].component]) { - this.navItems.splice(i, 1); + } + if (this.navItems[i].component) { + if (!this.loadedComponents[this.navItems[i].component]) { + this.navItems.splice(i, 1); } } } diff --git a/src/app/core/substance-form/simplified-names/simplified-name-form.component.html b/src/app/core/substance-form/simplified-names/simplified-name-form.component.html index ed2acefce..39526f5ad 100644 --- a/src/app/core/substance-form/simplified-names/simplified-name-form.component.html +++ b/src/app/core/substance-form/simplified-names/simplified-name-form.component.html @@ -24,6 +24,7 @@ [(ngModel)]="name.name" required name="name" + rows="1" (keypress)="preventNewLine($event)" > diff --git a/src/app/core/substance-form/simplified-names/simplified-name-form.component.scss b/src/app/core/substance-form/simplified-names/simplified-name-form.component.scss index 277f9e8fb..d3ca9078d 100644 --- a/src/app/core/substance-form/simplified-names/simplified-name-form.component.scss +++ b/src/app/core/substance-form/simplified-names/simplified-name-form.component.scss @@ -13,9 +13,6 @@ color: var(--primary-color); } -.text-area{ - height:12px -} .notification-backdrop { position: absolute; From fe64f502149af81e1334d0575b392f0ad9a9079a Mon Sep 17 00:00:00 2001 From: Mitch Miller Date: Fri, 10 Apr 2026 14:24:23 -0400 Subject: [PATCH 362/408] allow every user with write access to see the display name radio buttons --- src/app/core/substance-form/names/name-form.component.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/core/substance-form/names/name-form.component.html b/src/app/core/substance-form/names/name-form.component.html index 6eed37e7f..72583f764 100644 --- a/src/app/core/substance-form/names/name-form.component.html +++ b/src/app/core/substance-form/names/name-form.component.html @@ -15,7 +15,7 @@
    -
    +
    {{ 'displayNameShortTitle' | elementLabel : 'substance_names_name' }} From 6ef0eb79ac66da0c1667c6a4fd6085297f66acbd Mon Sep 17 00:00:00 2001 From: Newatia Date: Fri, 10 Apr 2026 14:58:36 -0400 Subject: [PATCH 363/408] fixed impurities import --- .../impurities-form/impurities-form.component.ts | 12 +++++++----- src/app/fda/impurities/service/impurities.service.ts | 4 ++-- .../invitro-pharmacology-summary-form.component.ts | 1 + .../product/product-form/product-form.component.ts | 1 + 4 files changed, 11 insertions(+), 7 deletions(-) diff --git a/src/app/fda/impurities/impurities-form/impurities-form.component.ts b/src/app/fda/impurities/impurities-form/impurities-form.component.ts index eb96c22f3..edf7afc46 100644 --- a/src/app/fda/impurities/impurities-form/impurities-form.component.ts +++ b/src/app/fda/impurities/impurities-form/impurities-form.component.ts @@ -8,7 +8,7 @@ import { AppNotification, NotificationType } from '@gsrs-core/main-notification' import { DomSanitizer, SafeUrl } from '@angular/platform-browser'; import { Subscription } from 'rxjs'; import { Title } from '@angular/platform-browser'; -import { jp } from 'jsonpath'; +import jp from 'jsonpath'; import { take, map } from 'rxjs/operators'; import * as moment from 'moment'; import * as _ from 'lodash'; @@ -31,10 +31,10 @@ import { SubstanceFormResults } from '@gsrs-core/substance-form/substance-form.m import { Impurities, ImpuritiesDetails, ImpuritiesUnspecified, SubRelationship, ValidationMessage } from '../model/impurities.model'; @Component({ - selector: 'app-impurities-form', - templateUrl: './impurities-form.component.html', - styleUrls: ['./impurities-form.component.scss'], - standalone: false + selector: 'app-impurities-form', + templateUrl: './impurities-form.component.html', + styleUrls: ['./impurities-form.component.scss'], + standalone: false }) export class ImpuritiesFormComponent implements OnInit, OnDestroy { @@ -128,6 +128,7 @@ export class ImpuritiesFormComponent implements OnInit, OnDestroy { // if ((record) && this.jsonValid(record)) { const response = JSON.parse(record); if (response) { + // scrub ids and audit information before saving in import json this.scrub(response); this.impuritiesService.loadImpurities(response); @@ -762,6 +763,7 @@ export class ImpuritiesFormComponent implements OnInit, OnDestroy { delete intVersionHolders[i].internalVersion; } + delete old['id']; delete old['creationDate']; delete old['createdBy']; delete old['modifiedBy']; diff --git a/src/app/fda/impurities/service/impurities.service.ts b/src/app/fda/impurities/service/impurities.service.ts index eefb3d794..25f426eae 100644 --- a/src/app/fda/impurities/service/impurities.service.ts +++ b/src/app/fda/impurities/service/impurities.service.ts @@ -150,7 +150,7 @@ export class ImpuritiesService extends BaseHttpService { } saveImpurities(): Observable { - const url = this.apiBaseUrl + `impurities`; + const url = this.apiBaseUrlWithEntityContext; const params = new HttpParams(); const options = { params: params, @@ -181,7 +181,7 @@ export class ImpuritiesService extends BaseHttpService { } validateImpur(): Observable { - const url = `${this.configService.configData.apiBaseUrl}api/v1/impurities/@validate`; + const url = this.apiBaseUrlWithEntityContext + '@validate'; return this.http.post(url, this.impurities); } diff --git a/src/app/fda/invitro-pharmacology/invitro-pharmacology-form/invitro-pharmacology-summary-form/invitro-pharmacology-summary-form.component.ts b/src/app/fda/invitro-pharmacology/invitro-pharmacology-form/invitro-pharmacology-summary-form/invitro-pharmacology-summary-form.component.ts index 19c63052a..80a0a5823 100644 --- a/src/app/fda/invitro-pharmacology/invitro-pharmacology-form/invitro-pharmacology-summary-form/invitro-pharmacology-summary-form.component.ts +++ b/src/app/fda/invitro-pharmacology/invitro-pharmacology-form/invitro-pharmacology-summary-form/invitro-pharmacology-summary-form.component.ts @@ -995,6 +995,7 @@ export class InvitroPharmacologySummaryFormComponent implements OnInit, OnDestro delete assayResults[i]._assayResults; } + delete old['id']; delete old['creationDate']; delete old['createdBy']; delete old['modifiedBy']; diff --git a/src/app/fda/product/product-form/product-form.component.ts b/src/app/fda/product/product-form/product-form.component.ts index 8849f8850..69553af73 100644 --- a/src/app/fda/product/product-form/product-form.component.ts +++ b/src/app/fda/product/product-form/product-form.component.ts @@ -1136,6 +1136,7 @@ export class ProductFormComponent implements OnInit, AfterViewInit, OnDestroy { delete intVersionHolders[i].internalVersion; } + delete old['id']; delete old['creationDate']; delete old['createdBy']; delete old['modifiedBy']; From 481d54c8ed008f6ef85c96e06fbdd893206fb149 Mon Sep 17 00:00:00 2001 From: Jaroslav Iha Date: Mon, 13 Apr 2026 12:50:58 +0200 Subject: [PATCH 364/408] fix hiding of pfda toolbar buttons --- src/styles/_material-overrides.scss | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/styles/_material-overrides.scss b/src/styles/_material-overrides.scss index 08cfe370a..e270f08a0 100644 --- a/src/styles/_material-overrides.scss +++ b/src/styles/_material-overrides.scss @@ -1080,9 +1080,10 @@ app-loading .mat-mdc-progress-spinner { // ============================================================================ // Tablet/Mobile: Hide most navigation buttons at 1350px, show only Logo, Menu, Search, and Login +// Note: scoped to :not(.pfda-toolbar) to avoid hiding pfda-toolbar buttons @media (max-width: 1350px) { - .mat-toolbar, - .mat-mdc-toolbar { + .mat-toolbar:not(.pfda-toolbar), + .mat-mdc-toolbar:not(.pfda-toolbar) { // Logo container - always visible > .logo-container { display: flex !important; From 3b282815c6ebc2b8c3bce672b2081615eef4af3d Mon Sep 17 00:00:00 2001 From: Iaroslav Moskviak Date: Mon, 13 Apr 2026 12:58:36 -0400 Subject: [PATCH 365/408] fixed dialog height --- .../substance-summary-card.component.ts | 1 + src/styles/_material-overrides.scss | 16 ++++++++++++++++ 2 files changed, 17 insertions(+) diff --git a/src/app/core/substances-browse/substance-summary-card/substance-summary-card.component.ts b/src/app/core/substances-browse/substance-summary-card/substance-summary-card.component.ts index 89fdcb737..d1cca4f72 100644 --- a/src/app/core/substances-browse/substance-summary-card/substance-summary-card.component.ts +++ b/src/app/core/substances-browse/substance-summary-card/substance-summary-card.component.ts @@ -301,6 +301,7 @@ export class SubstanceSummaryCardComponent implements OnInit { minWidth: "40%", maxWidth: "90%", height: "90%", + panelClass: "show-molfile-dialog", data: { uuid: this.substance.uuid, approval: this.substance.approvalID }, }); this.overlayContainer.style.zIndex = "1002"; diff --git a/src/styles/_material-overrides.scss b/src/styles/_material-overrides.scss index ce80c078d..6e549fb88 100644 --- a/src/styles/_material-overrides.scss +++ b/src/styles/_material-overrides.scss @@ -851,6 +851,22 @@ button.mat-mdc-menu-item[ng-reflect-menu], } } +// Show Molfile dialog — needs to fill the 90% height passed in dialog.open() +// The global fit-content rule on mat-mdc-dialog-inner-container collapses it otherwise. +.show-molfile-dialog { + .mat-mdc-dialog-inner-container, + .mdc-dialog__surface { + height: 100% !important; + display: flex !important; + flex-direction: column !important; + } + + .mat-mdc-dialog-content { + flex: 1 !important; + max-height: none !important; + } +} + // Cross Entity Search dialog — MDC caps surface at 560px by default; override per panel class .cross-entity-search-dialog { .mdc-dialog__surface { From 6183bc0e4277c867f54cdfb685b3d42549778b03 Mon Sep 17 00:00:00 2001 From: Jaroslav Iha Date: Tue, 14 Apr 2026 14:46:08 +0200 Subject: [PATCH 366/408] update: layout of pFDA main page + pfda toolbar --- .../pfda-toolbar/pfda-toolbar.component.html | 19 +++- .../pfda-toolbar/pfda-toolbar.component.scss | 58 ++++++++++ .../pfda-toolbar/pfda-toolbar.component.ts | 8 +- src/app/core/home/home.component.html | 101 +++++++++--------- 4 files changed, 130 insertions(+), 56 deletions(-) diff --git a/src/app/core/base/pfda-toolbar/pfda-toolbar.component.html b/src/app/core/base/pfda-toolbar/pfda-toolbar.component.html index 1d0177b47..90b0b6f29 100644 --- a/src/app/core/base/pfda-toolbar/pfda-toolbar.component.html +++ b/src/app/core/base/pfda-toolbar/pfda-toolbar.component.html @@ -17,12 +17,21 @@
    - -
    - -
    GSRS
    +
    diff --git a/src/app/core/base/pfda-toolbar/pfda-toolbar.component.scss b/src/app/core/base/pfda-toolbar/pfda-toolbar.component.scss index d5e1fc63f..4e6d17e44 100644 --- a/src/app/core/base/pfda-toolbar/pfda-toolbar.component.scss +++ b/src/app/core/base/pfda-toolbar/pfda-toolbar.component.scss @@ -87,3 +87,61 @@ $screenMedium: 1045px; white-space: nowrap; text-overflow: ellipsis; } + +.gsrs-menu-trigger { + position: relative; + display: inline-block; + + .gsrs-dropdown { + display: none; + position: absolute; + top: 100%; + left: 0; + z-index: 1001; + background: #fff; + min-width: 112px; + max-width: 280px; + border-radius: 4px; + box-shadow: 0px 2px 4px -1px rgba(0,0,0,0.2), 0px 4px 5px 0px rgba(0,0,0,0.14), 0px 1px 10px 0px rgba(0,0,0,0.12); + padding: 0; + + a { + display: flex; + align-items: center; + height: 48px; + padding: 0 16px; + color: rgba(0,0,0,0.87); + text-decoration: none; + white-space: nowrap; + font-family: Roboto, "Helvetica Neue", sans-serif; + font-size: 14px; + font-weight: 400; + line-height: 48px; + cursor: pointer; + box-sizing: border-box; + + &:hover { + background: rgba(0,0,0,0.04); + } + } + } + + &:hover .gsrs-dropdown { + display: block; + } +} + + +::ng-deep .reg-a { + .mat-mdc-menu-content { + padding: 0; + width: 100%; + box-sizing: border-box; + } + + .mat-mdc-menu-item { + width: 100%; + box-sizing: border-box; + padding: 0 16px; + } +} diff --git a/src/app/core/base/pfda-toolbar/pfda-toolbar.component.ts b/src/app/core/base/pfda-toolbar/pfda-toolbar.component.ts index 39f294013..97ff539a6 100644 --- a/src/app/core/base/pfda-toolbar/pfda-toolbar.component.ts +++ b/src/app/core/base/pfda-toolbar/pfda-toolbar.component.ts @@ -1,4 +1,4 @@ -import { Component, OnInit } from '@angular/core'; +import { Component, OnInit, OnDestroy } from '@angular/core'; import { Router, ActivatedRoute, NavigationExtras } from '@angular/router'; import { ConfigService } from '../../config/config.service'; import { OverlayContainer } from '@angular/cdk/overlay'; @@ -13,7 +13,7 @@ import { NavItem } from '@gsrs-core/config'; templateUrl: './pfda-toolbar.component.html', styleUrls: ['./pfda-toolbar.component.scss'] }) -export class PfdaToolbarComponent implements OnInit { +export class PfdaToolbarComponent implements OnInit, OnDestroy { pfdaBaseUrl: string; supportEmail: string; logoSrcPath: string; @@ -101,4 +101,8 @@ export class PfdaToolbarComponent implements OnInit { logout(): void { this.authService.logout(); } + + ngOnDestroy(): void { + this.subscriptions.forEach(sub => sub.unsubscribe()); + } } diff --git a/src/app/core/home/home.component.html b/src/app/core/home/home.component.html index b8d9e65fa..69fab3579 100644 --- a/src/app/core/home/home.component.html +++ b/src/app/core/home/home.component.html @@ -26,55 +26,58 @@ - - - Structure Search - - - - - Sequence Search - - - - - Advanced Search - - - - - - - - Other - - - - - - - - - Browse Applications - - - - - - - Browse Products - - - - - - - Browse Clinical Trials - - - - From 72b825596ea6fce7f7ab3e29631c406a6aa2a5bc Mon Sep 17 00:00:00 2001 From: Iaroslav Moskviak Date: Tue, 14 Apr 2026 18:34:02 -0400 Subject: [PATCH 367/408] disulfide fix --- .../disulfide-links-form.component.html | 31 +++-- .../disulfide-links-form.component.ts | 124 ++++++++++-------- ...nce-form-disulfide-links-card.component.ts | 34 +++-- .../substance-products.component.scss | 8 +- src/styles/_material-overrides.scss | 6 +- 5 files changed, 123 insertions(+), 80 deletions(-) diff --git a/src/app/core/substance-form/disulfide-links/disulfide-links-form.component.html b/src/app/core/substance-form/disulfide-links/disulfide-links-form.component.html index bd5a3d946..af6d7582e 100644 --- a/src/app/core/substance-form/disulfide-links/disulfide-links-form.component.html +++ b/src/app/core/substance-form/disulfide-links/disulfide-links-form.component.html @@ -11,22 +11,31 @@
    -
    - - {{index === 0? 'To': 'From'}} - +
    + + {{ index === 0 ? "To" : "From" }} + - {{cys.subunitIndex}}_{{cys.residueIndex}} + {{ cys.subunitIndex }}_{{ cys.residueIndex }} - - {{site.subunitIndex}}_{{site.residueIndex}} + + {{ site.subunitIndex }}_{{ site.residueIndex }} - -
    +
    +
    - -
    +

    diff --git a/src/app/core/substance-form/disulfide-links/disulfide-links-form.component.ts b/src/app/core/substance-form/disulfide-links/disulfide-links-form.component.ts index dbff377f7..ed21aac16 100644 --- a/src/app/core/substance-form/disulfide-links/disulfide-links-form.component.ts +++ b/src/app/core/substance-form/disulfide-links/disulfide-links-form.component.ts @@ -1,34 +1,39 @@ -import {AfterViewInit, Component, EventEmitter, Input, OnDestroy, OnInit, Output} from '@angular/core'; -import {Link, Site} from '@gsrs-core/substance'; -import { SubstanceFormDisulfideLinksService } from './substance-form-disulfide-links.service'; -import {UtilsService} from '@gsrs-core/utils'; -import {ControlledVocabularyService} from '@gsrs-core/controlled-vocabulary'; -import {MatDialog} from '@angular/material/dialog'; -import {OverlayContainer} from '@angular/cdk/overlay'; -import {Subscription} from 'rxjs'; -import {SubstanceFormService} from '@gsrs-core/substance-form/substance-form.service'; -import {FormControl, FormGroup, Validators} from '@angular/forms'; -import {SubunitSelectorDialogComponent} from '@gsrs-core/substance-form/subunit-selector-dialog/subunit-selector-dialog.component'; +import { + AfterViewInit, + Component, + EventEmitter, + Input, + OnDestroy, + OnInit, + Output, +} from "@angular/core"; +import { Link, Site } from "@gsrs-core/substance"; +import { SubstanceFormDisulfideLinksService } from "./substance-form-disulfide-links.service"; +import { UtilsService } from "@gsrs-core/utils"; +import { ControlledVocabularyService } from "@gsrs-core/controlled-vocabulary"; +import { MatDialog } from "@angular/material/dialog"; +import { OverlayContainer } from "@angular/cdk/overlay"; +import { Subscription } from "rxjs"; +import { SubstanceFormService } from "@gsrs-core/substance-form/substance-form.service"; +import { FormControl, FormGroup, Validators } from "@angular/forms"; +import { SubunitSelectorDialogComponent } from "@gsrs-core/substance-form/subunit-selector-dialog/subunit-selector-dialog.component"; @Component({ - selector: 'app-disulfide-links-form', - templateUrl: './disulfide-links-form.component.html', - styleUrls: ['./disulfide-links-form.component.scss'], - standalone: false + selector: "app-disulfide-links-form", + templateUrl: "./disulfide-links-form.component.html", + styleUrls: ["./disulfide-links-form.component.scss"], + standalone: false, }) -export class DisulfideLinksFormComponent implements OnInit, AfterViewInit, OnDestroy { - +export class DisulfideLinksFormComponent + implements OnInit, AfterViewInit, OnDestroy +{ private privateLink: Link; public cysteine: Array = []; @Output() linkDeleted = new EventEmitter(); deleteTimer: any; testForm = new FormGroup({ - site0: new FormControl('', [ - Validators.required - ]), - site1: new FormControl('', [ - Validators.required - ]), + site0: new FormControl(null, [Validators.required]), + site1: new FormControl(null, [Validators.required]), }); private subscriptions: Array = []; private overlayContainer: HTMLElement; @@ -39,29 +44,32 @@ export class DisulfideLinksFormComponent implements OnInit, AfterViewInit, OnDes private utilsService: UtilsService, private overlayContainerService: OverlayContainer, private substanceFormService: SubstanceFormService, - private substanceFormDisulfideLinksService: SubstanceFormDisulfideLinksService - ) { } + private substanceFormDisulfideLinksService: SubstanceFormDisulfideLinksService, + ) {} ngOnInit() { if (this.privateLink.sites) { - this.testForm.controls['site0'].setValue(this.privateLink.sites[0].toString()); - this.testForm.controls['site1'].setValue(this.privateLink.sites[1].toString()); + this.testForm.controls["site0"].setValue(this.privateLink.sites[0]); + this.testForm.controls["site1"].setValue(this.privateLink.sites[1]); } else { this.privateLink.sites = [{}, {}]; } this.overlayContainer = this.overlayContainerService.getContainerElement(); } - ngAfterViewInit() { - setTimeout(() => { - const cysteineSubscription = this.substanceFormDisulfideLinksService.substanceCysteineSites.subscribe(cysteine => { - this.cysteine = cysteine; - }); - this.subscriptions.push(cysteineSubscription); - }); + ngAfterViewInit() { + setTimeout(() => { + const cysteineSubscription = + this.substanceFormDisulfideLinksService.substanceCysteineSites.subscribe( + (cysteine) => { + this.cysteine = cysteine; + }, + ); + this.subscriptions.push(cysteineSubscription); + }); } ngOnDestroy() { - this.subscriptions.forEach(subscription => { + this.subscriptions.forEach((subscription) => { subscription.unsubscribe(); }); } @@ -76,14 +84,14 @@ export class DisulfideLinksFormComponent implements OnInit, AfterViewInit, OnDes } deleteLink(): void { - if (confirm('Are you sure you want to delete links?')) { - this.privateLink.$$deletedCode = this.utilsService.newUUID(); - // if (!this.privateLink) { + if (confirm("Are you sure you want to delete links?")) { + this.privateLink.$$deletedCode = this.utilsService.newUUID(); + // if (!this.privateLink) { this.deleteTimer = setTimeout(() => { this.linkDeleted.emit(this.link); }, 1000); - // } - this.substanceFormDisulfideLinksService.emitDisulfideLinkUpdate(); + // } + this.substanceFormDisulfideLinksService.emitDisulfideLinkUpdate(); } } @@ -98,8 +106,11 @@ export class DisulfideLinksFormComponent implements OnInit, AfterViewInit, OnDes } updateSuggestions(value: Site, pos: number): void { - this.cysteine = this.cysteine.filter(function(r) { - return (r.residueIndex !== value.residueIndex) || (r.subunitIndex !== value.subunitIndex); + this.cysteine = this.cysteine.filter(function (r) { + return ( + r.residueIndex !== value.residueIndex || + r.subunitIndex !== value.subunitIndex + ); }); if (this.privateLink.sites[pos] !== value) { if (this.privateLink.sites[pos].residueIndex) { @@ -109,7 +120,7 @@ export class DisulfideLinksFormComponent implements OnInit, AfterViewInit, OnDes this.substanceFormDisulfideLinksService.updateCysteine(this.cysteine); } else { } - this.testForm.controls['site' + pos].setValue(value); + this.testForm.controls["site" + pos].setValue(value); } openDialog(): void { @@ -118,28 +129,28 @@ export class DisulfideLinksFormComponent implements OnInit, AfterViewInit, OnDes sentSites = []; } const dialogRef = this.dialog.open(SubunitSelectorDialogComponent, { - data: {'card': 'disulfide', 'link': sentSites}, - width: '1040px', - panelClass: 'subunit-dialog' + data: { card: "disulfide", link: sentSites }, + width: "1040px", + panelClass: "subunit-dialog", }); - this.overlayContainer.style.zIndex = '1002'; + this.overlayContainer.style.zIndex = "1002"; - const dialogSubscription = dialogRef.afterClosed().subscribe(newLinks => { + const dialogSubscription = dialogRef.afterClosed().subscribe((newLinks) => { this.overlayContainer.style.zIndex = null; if (newLinks) { if (newLinks[0] && newLinks[0].subunitIndex) { this.privateLink.sites[0] = newLinks[0]; - this.testForm.controls['site0'].setValue(this.privateLink.sites[0].toString()); + this.testForm.controls["site0"].setValue(this.privateLink.sites[0]); } else { this.privateLink.sites[0] = {}; - this.testForm.controls['site0'].reset(); + this.testForm.controls["site0"].reset(); } if (newLinks[1] && newLinks[1].subunitIndex) { this.privateLink.sites[1] = newLinks[1]; - this.testForm.controls['site1'].setValue(this.privateLink.sites[1].toString()); + this.testForm.controls["site1"].setValue(this.privateLink.sites[1]); } else { this.privateLink.sites[1] = {}; - this.testForm.controls['site1'].reset(); + this.testForm.controls["site1"].reset(); } } this.substanceFormDisulfideLinksService.emitDisulfideLinkUpdate(); @@ -147,4 +158,13 @@ export class DisulfideLinksFormComponent implements OnInit, AfterViewInit, OnDes this.subscriptions.push(dialogSubscription); } + compareSites = (a: Site | null, b: Site | null): boolean => { + if (!a || !b) { + return a === b; + } + + return ( + a.subunitIndex === b.subunitIndex && a.residueIndex === b.residueIndex + ); + }; } diff --git a/src/app/core/substance-form/disulfide-links/substance-form-disulfide-links-card.component.ts b/src/app/core/substance-form/disulfide-links/substance-form-disulfide-links-card.component.ts index 3d4a1b355..77a57d111 100644 --- a/src/app/core/substance-form/disulfide-links/substance-form-disulfide-links-card.component.ts +++ b/src/app/core/substance-form/disulfide-links/substance-form-disulfide-links-card.component.ts @@ -42,22 +42,28 @@ export class SubstanceFormDisulfideLinksCardComponent extends SubstanceCardBaseF } ngAfterViewInit() { - const disulfideLinksSubscription = this.substanceFormDisulfideLinksService.substanceDisulfideLinks.subscribe(disulfideLinks => { - this.disulfideLinks = disulfideLinks; - this.countCysteine(); - }); + // setTimeout defers subscriptions until after the service's unload/init setTimeout callbacks + // have fired. This prevents subscribing to a propertyEmitter that is about to be completed + // and replaced by unloadSubstance(), which would leave the component with a dead subscription. + setTimeout(() => { + const disulfideLinksSubscription = this.substanceFormDisulfideLinksService.substanceDisulfideLinks.subscribe(disulfideLinks => { + this.disulfideLinks = disulfideLinks; + this.countCysteine(); + }); + this.subscriptions.push(disulfideLinksSubscription); - this.subscriptions.push(disulfideLinksSubscription); - const subunitsSubscription = this.substanceFormService.substanceSubunits.subscribe(subunits => { - this.subunits = subunits; - this.countCysteine(); - }); - this.subscriptions.push(subunitsSubscription); - const cysteineSubscription = this.substanceFormDisulfideLinksService.substanceCysteineSites.subscribe(cysteine => { - this.cysteine = cysteine; - this.countCysteine(); + const subunitsSubscription = this.substanceFormService.substanceSubunits.subscribe(subunits => { + this.subunits = subunits; + this.countCysteine(); + }); + this.subscriptions.push(subunitsSubscription); + + const cysteineSubscription = this.substanceFormDisulfideLinksService.substanceCysteineSites.subscribe(cysteine => { + this.cysteine = cysteine; + this.countCysteine(); + }); + this.subscriptions.push(cysteineSubscription); }); - this.subscriptions.push(cysteineSubscription); } ngOnDestroy() { diff --git a/src/app/fda/substance-details/substance-products/substance-products.component.scss b/src/app/fda/substance-details/substance-products/substance-products.component.scss index ad142db50..7bc840da1 100644 --- a/src/app/fda/substance-details/substance-products/substance-products.component.scss +++ b/src/app/fda/substance-details/substance-products/substance-products.component.scss @@ -7,10 +7,16 @@ } .mat-tab-style { + :host ::ng-deep .mat-mdc-tab .mdc-tab__text-label, .mat-tab-labels, .mat-tab-label, .mat-tab-link { - color: var(--regular-blue-color); + color: var(--regular-blue-color); } +} +:host ::ng-deep .mat-mdc-tab-list .mat-mdc-tab, +:host ::ng-deep .mat-tab-list .mat-tab-label { + flex-grow: 0 !important; + flex-shrink: 0 !important; } .bordergray { diff --git a/src/styles/_material-overrides.scss b/src/styles/_material-overrides.scss index 6e549fb88..59a87649b 100644 --- a/src/styles/_material-overrides.scss +++ b/src/styles/_material-overrides.scss @@ -1332,10 +1332,12 @@ button.export-button { 0px 3px 14px 2px rgba(0, 0, 0, 0.12); } -// Override MDC ripple behavior if it interferes with existing styles +// Override MDC ripple behavior if it interferes with existing styles. +// NOTE: .mat-ripple is intentionally excluded — MatRipple directive adds that class +// to its HOST element (e.g. the pagination

    diff --git a/src/app/core/base/pfda-toolbar/pfda-toolbar.component.scss b/src/app/core/base/pfda-toolbar/pfda-toolbar.component.scss index 4e6d17e44..b24eacaba 100644 --- a/src/app/core/base/pfda-toolbar/pfda-toolbar.component.scss +++ b/src/app/core/base/pfda-toolbar/pfda-toolbar.component.scss @@ -97,7 +97,7 @@ $screenMedium: 1045px; position: absolute; top: 100%; left: 0; - z-index: 1001; + z-index: 1002; background: #fff; min-width: 112px; max-width: 280px; diff --git a/src/app/core/home/home.component.html b/src/app/core/home/home.component.html index 69fab3579..e27306813 100644 --- a/src/app/core/home/home.component.html +++ b/src/app/core/home/home.component.html @@ -36,15 +36,15 @@ Sequence Search - - - Advanced Search - - Bulk Search + + + + Advanced Search + From 6d65c8ec9d2ac0127783e3fef57f446753e262e8 Mon Sep 17 00:00:00 2001 From: Jaroslav Iha Date: Wed, 15 Apr 2026 16:09:47 +0200 Subject: [PATCH 369/408] update z-index of header --- src/app/core/base/base.component.scss | 2 +- src/styles/_material-overrides.scss | 1665 +++++++++++++++++++++++++ 2 files changed, 1666 insertions(+), 1 deletion(-) create mode 100644 src/styles/_material-overrides.scss diff --git a/src/app/core/base/base.component.scss b/src/app/core/base/base.component.scss index 8dca2150c..35ff366e1 100644 --- a/src/app/core/base/base.component.scss +++ b/src/app/core/base/base.component.scss @@ -111,7 +111,7 @@ .mat-toolbar { position: fixed; top: 0; - z-index: 1001; + z-index: 1002; } .logo { diff --git a/src/styles/_material-overrides.scss b/src/styles/_material-overrides.scss new file mode 100644 index 000000000..dac5b3ac6 --- /dev/null +++ b/src/styles/_material-overrides.scss @@ -0,0 +1,1665 @@ +/** + * Global Angular Material Overrides + * + * Project-wide overrides for Angular Material (MDC) components. + * Loaded last in main.scss so these rules take precedence over Material's + * generated theme styles. + */ + +// ============================================================================ +// MAT-CARD FIXES +// ============================================================================ + +// Fix MDC card padding to match legacy behavior +.mat-mdc-card { + // Angular 15 MDC cards have padding: 16px by default + // Ensure consistency with Angular 14 legacy cards + padding: 16px; + margin: 0 auto 20px auto; + max-width: 1228px; + width: 100%; + box-sizing: border-box; + + &:not([class*="mat-elevation-z"]) { + box-shadow: + 0px 2px 1px -1px rgba(0, 0, 0, 0.2), + 0px 1px 1px 0px rgba(0, 0, 0, 0.14), + 0px 1px 3px 0px rgba(0, 0, 0, 0.12); + } +} + +// Fix MDC card header to match legacy +.mat-mdc-card-header { + display: flex; + padding: 0; +} + +// Fix MDC card title spacing +.mat-mdc-card-title { + font-size: 24px; + font-weight: 500; + margin-top: 0 !important; + margin-bottom: 0 !important; +} + +// Fix MDC card subtitle +.mat-mdc-card-subtitle { + margin-top: 0 !important; + margin-bottom: 12px !important; +} + +// Fix MDC card content spacing +.mat-mdc-card-content { + display: block; + + &:first-child { + padding-top: 0; + } + + &:last-child { + padding-bottom: 0; + } +} + +// ============================================================================ +// MAT-CHIP FIXES +// ============================================================================ + +// Fix chip styling for consistency with Angular 14 +.mat-mdc-chip { + &.mat-mdc-standard-chip { + min-height: 32px; + --mdc-chip-container-height: 32px; + } + + .mdc-evolution-chip__action--primary { + padding-left: 12px; + padding-right: 12px; + } + + .mdc-evolution-chip__text-label { + font-size: 14px; + } +} + +.mat-mdc-standard-chip:not(.mdc-evolution-chip--disabled) { + --mat-chip-elevated-container-color: var(--primary-color); + --mat-chip-label-text-color: #fff; /* optional */ +} + +// Chip spacing for both chip-set and chip-listbox (deprecated) +.mat-mdc-chip-set .mat-mdc-chip, +.mat-mdc-chip-listbox .mat-mdc-chip { + margin: 4px; +} + +// ============================================================================ +// MAT-FORM-FIELD FIXES +// ============================================================================ + +// MDC form fields have different default appearance and structure +.mat-mdc-form-field { + // Match Angular 14 form field appearance + font-family: Roboto, "Helvetica Neue", sans-serif !important; + // font-size: 14px !important; + // line-height: 1.125 !important; + + // Fix the wrapper to not add extra spacing + .mat-mdc-text-field-wrapper { + padding-bottom: 0; + background-color: transparent; + + .mat-mdc-form-field-flex { + align-items: center; + } + } + + // Fix fill appearance to match legacy + &.mat-form-field-appearance-fill { + .mat-mdc-text-field-wrapper { + padding-bottom: 0; + } + + .mdc-text-field { + background-color: transparent; + border-radius: 4px 4px 0 0; + // padding: 0 12px; + } + + .mdc-text-field--filled { + &:not(.mdc-text-field--disabled) { + background-color: transparent; + } + + .mdc-line-ripple::after { + border-bottom-width: 2px; + } + } + + // Fix infix padding + .mat-mdc-form-field-infix { + min-height: auto; + // padding: 25px 0 0.4375em 0; + } + + // Fix label positioning + .mat-mdc-floating-label { + top: 28px; + } + } + + // Fix outline appearance + &.mat-form-field-appearance-outline { + .mdc-text-field { + padding: 0; + } + + .mdc-text-field--outlined { + .mdc-notched-outline { + .mdc-notched-outline__leading, + .mdc-notched-outline__notch, + .mdc-notched-outline__trailing { + border-color: rgba(0, 0, 0, 0.38); + border-width: 1px; + } + } + + &:not(.mdc-text-field--disabled) { + &:hover .mdc-notched-outline { + .mdc-notched-outline__leading, + .mdc-notched-outline__notch, + .mdc-notched-outline__trailing { + border-color: rgba(0, 0, 0, 0.87); + } + } + } + } + + .mat-mdc-form-field-infix { + padding-top: 16px; + padding-bottom: 16px; + } + + .mat-mdc-floating-label { + top: 28px; + } + } + + // Fix input and label alignment + .mat-mdc-input-element { + font: inherit; + } + + // Fix label styling + .mat-mdc-floating-label { + font-size: 14px; + font-weight: 400; + } + + // Beat Material's runtime: .mdc-text-field--filled .mdc-floating-label { font-size: Xrem } + // That rule has specificity (0,2,0). Adding .mat-mdc-form-field parent gives us (0,3,0). + .mdc-text-field .mdc-floating-label { + font-size: var(--floating-label-font-size, 14px); + } + + // Fix subscript wrapper (hints and errors) + .mat-mdc-form-field-subscript-wrapper { + font-size: 12px; + margin-top: 0.66667em; + padding: 0; + + .mat-mdc-form-field-hint-wrapper, + .mat-mdc-form-field-error-wrapper { + padding: 0; + } + } + + // Fix bottom spacing + .mat-mdc-form-field-bottom-align::before { + content: none; + } + + // Fix icon button inside form field + .mat-mdc-icon-button { + width: 36px; + height: 36px; + + .mat-icon { + font-size: 20px; + width: 20px; + height: 20px; + } + } + + // Fix prefix and suffix icon alignment + .mat-mdc-form-field-icon-prefix, + .mat-mdc-form-field-icon-suffix { + display: inline-flex; + align-items: center; + justify-content: center; + + .mat-icon { + display: flex; + align-items: center; + justify-content: center; + } + } +} + +// Legacy form field icon fixes +.mat-form-field-prefix, +.mat-form-field-suffix { + .mat-icon, + .mat-icon-button { + display: inline-flex; + align-items: center; + justify-content: center; + } +} + +// ============================================================================ +// MAT-BUTTON FIXES +// ============================================================================ + +// Fix button styling for consistency with Angular 14 +.mat-mdc-button .mdc-button__label, +.mat-mdc-raised-button .mdc-button__label, +.mat-mdc-unelevated-button .mdc-button__label, +.mat-mdc-outlined-button .mdc-button__label { + white-space: nowrap; +} +.mat-mdc-button, +.mat-mdc-raised-button, +.mat-mdc-unelevated-button, +.mat-mdc-outlined-button { + // Match Angular 14 button heights and appearance + --mdc-text-button-container-height: 36px; + --mdc-filled-button-container-height: 36px; + --mdc-outlined-button-container-height: 36px; + --mdc-protected-button-container-height: 36px; + + font-family: Roboto, "Helvetica Neue", sans-serif; + font-size: 14px; + font-weight: 500; + line-height: 36px; + min-width: 64px; + padding: 0 16px; + + // CRITICAL: Ensure button content respects DOM order + display: inline-flex; + flex-direction: row; + align-items: center; + + // .mdc-button__label { + // font-size: 16px; + // font-weight: 500; + // line-height: normal; + // display: flex; + // flex-direction: row; + // align-items: center; + // order: 0; // Ensure label stays in natural order + // } + + // Ensure mat-icons stay in their HTML order + .mat-icon { + order: 0; // Don't reorder + } + + // Fix the persistent ripple that can cause visual issues + .mat-mdc-button-persistent-ripple { + border-radius: 4px; + } + + // Ensure touch target doesn't break layout + .mat-mdc-button-touch-target { + height: 100%; + } +} + +// Fix raised button elevation +.mat-mdc-raised-button:not(:disabled) { + box-shadow: + 0px 3px 1px -2px rgba(0, 0, 0, 0.2), + 0px 2px 2px 0px rgba(0, 0, 0, 0.14), + 0px 1px 5px 0px rgba(0, 0, 0, 0.12); + + &:hover { + box-shadow: + 0px 2px 4px -1px rgba(0, 0, 0, 0.2), + 0px 4px 5px 0px rgba(0, 0, 0, 0.14), + 0px 1px 10px 0px rgba(0, 0, 0, 0.12); + } +} + +// Fix flat button (now called unelevated in MDC) +.mat-mdc-unelevated-button { + --mdc-filled-button-container-height: 36px; +} + +// Ensure icon buttons have consistent size +.mat-mdc-icon-button { + --mdc-icon-button-state-layer-size: 40px; + width: 40px; + height: 40px; + padding: 8px; + line-height: 24px; + + .mat-mdc-button-touch-target { + width: 48px; + height: 48px; + } + + .mat-icon { + width: 24px; + height: 24px; + font-size: 24px; + line-height: 24px; + } +} + +// Fix FAB button +.mat-mdc-fab, +.mat-mdc-mini-fab { + .mat-mdc-button-touch-target { + width: 100%; + height: 100%; + } +} + +// ============================================================================ +// MAT-ICON FIXES +// ============================================================================ + +// Fix icon sizing and alignment +.mat-icon { + width: 24px; + height: 24px; + font-size: 24px; + line-height: 24px; + display: inline-flex; + align-items: center; + justify-content: center; + vertical-align: middle; + flex-shrink: 0; + + // Critical: Fix SVG positioning to center + svg { + width: 100%; + height: 100%; + fill: currentColor; + display: block; + margin: auto; + } +} + +// Specific fix for mat-icons inside buttons +button .mat-icon, +.mat-button .mat-icon, +.mat-raised-button .mat-icon, +.mat-flat-button .mat-icon, +.mat-stroked-button .mat-icon, +.mat-mdc-button .mat-icon, +.mat-mdc-raised-button .mat-icon, +.mat-mdc-unelevated-button .mat-icon, +.mat-mdc-outlined-button .mat-icon { + display: inline-flex; + align-items: center; + justify-content: center; + vertical-align: middle; +} + +// Fix icon button sizing (already covered above but ensure consistency) +.mat-mdc-icon-button { + display: inline-flex; + align-items: center; + justify-content: center; + + .mat-icon { + position: relative; + left: 0; + right: 0; + top: 0; + bottom: 0; + margin: auto; + } + + .mat-mdc-button-touch-target { + position: absolute; + } +} + +.mat-icon-button { + display: inline-flex; + align-items: center; + justify-content: center; + + .mat-icon { + position: relative; + margin: auto; + } +} + +// ============================================================================ +// MAT-MENU FIXES +// ============================================================================ + +// Fix menu panel +.mat-mdc-menu-panel { + min-width: 112px; + max-width: 280px; + border-radius: 4px; + box-shadow: + 0px 2px 4px -1px rgba(0, 0, 0, 0.2), + 0px 4px 5px 0px rgba(0, 0, 0, 0.14), + 0px 1px 10px 0px rgba(0, 0, 0, 0.12); +} + +.mat-mdc-menu-content { + padding: 0; + width: 100%; + box-sizing: border-box; + + // Ensure all menu items have consistent vertical rhythm + > * { + margin: 0 !important; + display: block !important; + } + + // Fix for wrapper divs (common pattern but not recommended) + > div { + display: contents !important; + margin: 0 !important; + padding: 0 !important; + height: auto !important; + } +} + +// Fix menu item styling +.mat-mdc-menu-item { + font-family: Roboto, "Helvetica Neue", sans-serif !important; + font-size: 14px !important; + font-weight: 400 !important; + min-height: 48px !important; + height: 48px !important; + padding: 0 16px !important; + display: flex !important; + align-items: center !important; + position: relative !important; + pointer-events: auto !important; + cursor: pointer !important; + width: 100% !important; + box-sizing: border-box !important; + margin: 0 !important; + line-height: 48px !important; + + .mat-icon { + margin-right: 16px; + line-height: normal !important; + } + + .mat-mdc-menu-item-text { + flex-grow: 1; + line-height: normal !important; + } + + // Ensure the MDC button inside menu item is clickable + .mat-mdc-menu-item-text, + .mdc-list-item__primary-text { + pointer-events: auto !important; + line-height: normal !important; + } + + // Fix for anchor tag menu items + &[href] { + pointer-events: auto !important; + cursor: pointer !important; + } + + // Fix internal content wrapper that Angular Material adds + .mdc-list-item__content { + display: flex !important; + align-items: center !important; + height: 48px !important; + padding: 0 !important; + margin: 0 !important; + } +} + +// Additional fixes for submenu trigger menu items (menu items with matMenuTriggerFor) +// The base .mat-mdc-menu-item rule above handles most of it, but ensure no overrides +.mat-mdc-menu-item.mat-mdc-menu-trigger, +a.mat-mdc-menu-item[ng-reflect-menu], +button.mat-mdc-menu-item[ng-reflect-menu], +.mat-mdc-menu-item.cdk-menu-trigger { + // Ensure submenu triggers don't have any extra spacing + vertical-align: middle !important; + + // Ensure the submenu indicator icon aligns properly + &::after { + line-height: normal !important; + } +} + +// Fix for nested menu positioning in Angular Material 19 MDC +// Ensure the CDK overlay positioning works correctly +.cdk-overlay-connected-position-bounding-box { + // Don't interfere with the calculated position + .mat-mdc-menu-panel { + // Ensure nested menus align properly with their trigger + &.mat-mdc-menu-nested { + margin-top: 0 !important; + } + } +} + +// ============================================================================ +// MAT-LIST FIXES +// ============================================================================ + +.mat-mdc-list, +.mat-mdc-list-base { + padding: 8px 0; + font-family: Roboto, "Helvetica Neue", sans-serif; +} + +.mat-mdc-list-item { + font-size: 14px; + font-weight: 400; + height: 48px; + + .mdc-list-item__primary-text { + font-size: 14px; + font-weight: 400; + color: var(--link-color); + } + + .mdc-list-item__secondary-text { + font-size: 12px; + font-weight: 400; + } + + // Prevent mat-icons from inheriting link-color from primary text + .mat-icon { + color: var(--mat-list-color); + } +} + +.mat-mdc-list-item-avatar { + width: 40px; + height: 40px; + border-radius: 50%; + margin-right: 16px; +} + +.mat-mdc-list-item-icon { + width: 24px; + height: 24px; + font-size: 24px; + margin-right: 16px; +} + +// Two-line list items +.mat-mdc-list-item.mdc-list-item--with-two-lines { + height: 64px; +} + +// Three-line list items +.mat-mdc-list-item.mdc-list-item--with-three-lines { + height: 88px; +} + +// ============================================================================ +// MAT-TABLE FIXES +// ============================================================================ + +// Fix table styling for consistency with Angular 14 +.mat-mdc-table { + background-color: inherit; + font-family: Roboto, "Helvetica Neue", sans-serif; +} + +.mat-mdc-header-row { + min-height: 56px; +} + +.mat-mdc-row { + min-height: 48px; +} + +.mat-mdc-header-cell { + font-size: 12px; + font-weight: 500; + color: rgba(0, 0, 0, 0.54); +} + +.mat-mdc-cell { + font-size: 14px; + color: rgba(0, 0, 0, 0.87); +} + +// ============================================================================ +// MAT-PAGINATOR FIXES +// ============================================================================ + +// Fix paginator styling to match Angular 14 +.mat-mdc-paginator { + background-color: transparent; + display: block; + font-family: Roboto, "Helvetica Neue", sans-serif; +} + +.mat-mdc-paginator-container { + display: flex; + align-items: center; + justify-content: flex-end; + min-height: 56px; + padding: 0 8px; +} + +.mat-mdc-paginator-page-size { + display: flex; + align-items: center; +} + +.mat-mdc-paginator-range-label { + margin: 0 32px 0 24px; +} + +.mat-mdc-paginator-page-size .mdc-notched-outline__leading, +.mat-mdc-paginator-page-size .mdc-notched-outline__trailing, +.mat-mdc-paginator-page-size .mdc-notched-outline__notch { + border-bottom: 1px solid currentColor !important; + border-radius: 0 !important; +} + +/* Adjust the overall infix height if needed, as padding affects height */ +.mat-mdc-paginator-page-size .mat-mdc-form-field-infix { + padding: 5px 0 !important; + min-height: auto !important; /* Ensure min-height does not enforce extra space */ +} + +/* Adjust the select's value text container height/line-height for vertical alignment */ +.mat-mdc-paginator-page-size .mat-mdc-select-value-text { + line-height: unset !important; + display: flex; + align-items: center; +} + +// ============================================================================ +// PAGE SELECTOR (GLOBAL) +// ============================================================================ + +// Page selector styles used across browse components +.page-selector { + display: flex; + flex-direction: row; + align-items: center; + margin-left: 20px; +} + +.page-label { + color: var(--dark-label-color); + display: block; + font-family: Roboto, "Helvetica Neue", sans-serif; + font-size: 14px; + padding-right: 12px; +} + +// Page selector form field adjustments +// Increase specificity to override the general mat-form-field-appearance-fill styles above +.page-selector > .mat-mdc-form-field.mat-form-field-appearance-fill { + .mat-mdc-form-field-infix { + min-height: auto; + padding: 10px 0 0 0 !important; + } + + .mdc-text-field { + padding: 0; + } +} + +// Responsive behavior - hide page selector on small screens +@media (max-width: 730px) { + .page-selector { + display: none !important; + } +} + +// ============================================================================ +// MAT-SELECT FIXES +// ============================================================================ + +// Fix select styling to match Angular 14 +.mat-mdc-select { + font-family: Roboto, "Helvetica Neue", sans-serif; + font-size: 14px; +} + +.mat-mdc-select-value { + font-size: 14px; +} + +.mat-mdc-select-trigger { + height: auto; +} + +.mat-mdc-select-panel { + max-height: 256px; +} + +.mat-mdc-option { + font-family: Roboto, "Helvetica Neue", sans-serif; + font-size: 14px; + min-height: 48px; + + .mdc-list-item__primary-text { + font-size: 14px !important; + } +} + +// ============================================================================ +// MAT-CHECKBOX FIXES +// ============================================================================ + +.mat-mdc-checkbox { + --mdc-checkbox-state-layer-size: 40px; + + .mdc-checkbox { + padding: calc((var(--mdc-checkbox-state-layer-size) - 18px) / 2); + } + + .mdc-checkbox__background { + width: 18px; + height: 18px; + top: calc((var(--mdc-checkbox-state-layer-size) - 18px) / 2); + left: calc((var(--mdc-checkbox-state-layer-size) - 18px) / 2); + } + + .mdc-form-field { + font-size: 14px; + } +} + +// ============================================================================ +// MAT-RADIO FIXES +// ============================================================================ + +.mat-mdc-radio-button { + .mdc-radio { + padding: 10px; + } + + .mdc-form-field { + font-size: 14px; + } +} + +// ============================================================================ +// MAT-EXPANSION-PANEL FIXES +// ============================================================================ + +.mat-expansion-panel { + box-shadow: + 0px 2px 1px -1px rgba(0, 0, 0, 0.2), + 0px 1px 1px 0px rgba(0, 0, 0, 0.14), + 0px 1px 3px 0px rgba(0, 0, 0, 0.12); +} + +.mat-expansion-panel-header { + font-family: Roboto, "Helvetica Neue", sans-serif; + font-size: 14px; + height: 48px; +} + +.mat-expansion-panel-content { + font-family: Roboto, "Helvetica Neue", sans-serif; +} + +// ============================================================================ +// MAT-DIALOG FIXES +// ============================================================================ + +.mat-mdc-dialog-container { + --mat-dialog-supporting-text-color: black; + + .mdc-dialog__surface { + border-radius: 4px; + padding: 24px; + } +} + +// User Edit dialog +.user-edit-dialog { + .mat-mdc-dialog-title { + font-size: 40px !important; + font-weight: bold !important; + } + + .mat-mdc-dialog-content { + padding: 10px 24px !important; + max-height: none !important; + overflow-y: unset !important; + } + + .mat-mdc-dialog-container, + .mdc-dialog__surface { + height: unset !important; + max-height: 90vh !important; + overflow-y: auto !important; + } +} + +// Cross Entity Search dialog — MDC caps surface at 560px by default; override per panel class +.cross-entity-search-dialog { + .mdc-dialog__surface { + max-width: none !important; + } +} + +// Advanced Selector dialog — override MDC's 560px max-width CSS variable at the pane level, +// which is where var(--mat-dialog-container-max-width, 560px) is resolved. Children that use +// max-width: inherit will then inherit none instead of 560px. +.advanced-selector-dialog { + --mat-dialog-container-max-width: none; + + .mat-mdc-dialog-inner-container, + .mdc-dialog__surface { + max-width: none !important; + } +} + +.mat-mdc-dialog-title { + font-size: 20px; + font-weight: 500; + margin: 0 0 16px; + padding: 24px 24px 0; +} + +.mat-mdc-dialog-content { + font-size: 14px; + padding: 0 24px; +} + +.mat-mdc-dialog-inner-container { + height: fit-content !important; + max-height: 90vh !important; + overflow-y: auto !important; +} + +.mat-mdc-dialog-actions { + padding: 0 24px 8px 24px !important; + min-height: 52px; +} + +// ============================================================================ +// MAT-TAB FIXES +// ============================================================================ + +.mat-mdc-tab-group { + font-family: Roboto, "Helvetica Neue", sans-serif; +} + +.mat-mdc-tab-list { + flex-grow: 0 !important; +} + +.mat-mdc-tab { + font-size: 14px; + font-weight: 500; + min-width: 160px; + height: 48px; +} + +.mat-mdc-tab-list .mat-mdc-tab, +.mat-tab-list .mat-tab-label { + letter-spacing: normal; + + .mdc-tab__text-label { + letter-spacing: normal; + line-height: 1.3; + } +} + +.mat-mdc-tab-body-content { + padding: 16px 0; +} + +// Admin tab group: 18px tab labels (beats Material runtime at 0,2,0) +.tab-group .mat-mdc-tab .mdc-tab__text-label { + font-size: 18px; +} + +// ============================================================================ +// MAT-SLIDER FIXES +// ============================================================================ + +.mat-mdc-slider { + .mdc-slider__track { + height: 2px; + } + + .mdc-slider__thumb-knob { + width: 12px; + height: 12px; + } +} + +// ============================================================================ +// MAT-PROGRESS-BAR FIXES +// ============================================================================ + +.mat-mdc-progress-bar { + --mdc-linear-progress-track-height: 4px; +} + +// ============================================================================ +// MAT-PROGRESS-SPINNER FIXES +// ============================================================================ + +.mat-mdc-progress-spinner { + circle { + stroke-width: 10%; + } +} + +// Scoped to app-loading only — prevents ALL spinners from being fixed/centered +app-loading .mat-mdc-progress-spinner { + position: fixed; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + z-index: 1003; + display: block; +} + +// ============================================================================ +// MAT-TOOLTIP FIXES +// ============================================================================ + +.mat-mdc-tooltip { + .mdc-tooltip__surface { + font-size: 10px; + padding: 4px 8px; + max-width: 200px; + } +} + +// ============================================================================ +// MAT-SNACKBAR FIXES +// ============================================================================ + +.mat-mdc-snack-bar-container { + .mdc-snackbar__surface { + background-color: #323232; + } + + .mdc-snackbar__label { + color: white; + font-size: 14px; + } +} + +// ============================================================================ +// MAT-TOOLBAR FIXES +// ============================================================================ + +.mat-toolbar, +.mat-mdc-toolbar { + font-family: Roboto, "Helvetica Neue", sans-serif; + font-size: 14px; + font-weight: 400; + display: flex; + box-sizing: border-box; + width: 100%; + flex-direction: row; + align-items: center; + white-space: nowrap; + padding: 0 16px; + min-height: 64px; + position: relative; + + // Single row toolbar (default) + &:not(.mat-toolbar-multiple-rows) { + flex-direction: row; + height: 64px; + background-color: var(--primary-color); + position: fixed; + top: 0; + z-index: 1002; + + // @media (max-width: 990px) { + // height: auto; + // flex-wrap: wrap; + // } + } + + // Multiple rows toolbar + &.mat-toolbar-multiple-rows { + flex-direction: column; + min-height: 64px; + } +} + +// Fix toolbar row heights to match Angular 14 +.mat-toolbar-row, +.mat-mdc-toolbar-row { + display: flex; + box-sizing: border-box; + width: 100%; + height: 64px; + flex-direction: row; + align-items: center; + white-space: nowrap; + padding: 0 16px; +} + +.mat-toolbar-single-row, +.mat-mdc-toolbar-single-row { + display: flex; + box-sizing: border-box; + width: 100%; + height: 64px; + flex-direction: row; + align-items: center; + white-space: nowrap; + padding: 0 16px; +} + +// Fix spacer/middle-fill pattern (flex-grow to push items to sides) +.mat-toolbar .middle-fill, +.mat-mdc-toolbar .middle-fill, +.mat-toolbar .spacer, +.mat-mdc-toolbar .spacer, +.mat-toolbar .fill, +.mat-mdc-toolbar .fill { + flex: 1 1 auto; +} + +// ============================================================================ +// RESPONSIVE TOOLBAR ADJUSTMENTS +// ============================================================================ + +// Tablet/Mobile: Hide most navigation buttons at 1350px, show only Logo, Menu, Search, and Login +// Note: scoped to :not(.pfda-toolbar) to avoid hiding pfda-toolbar buttons +@media (max-width: 1350px) { + .mat-toolbar:not(.pfda-toolbar), + .mat-mdc-toolbar:not(.pfda-toolbar) { + // Logo container - always visible + > .logo-container { + display: flex !important; + } + + // Menu button (nav-small span) - always visible + > span.nav-small { + display: inline-block !important; + } + + // Hide all div containers EXCEPT logo-container and the one containing login + > div { + // Hide by default + display: none !important; + + // But keep logo container visible + &.logo-container { + display: flex !important; + } + + // Keep the div that contains login button or logged-in user visible + &:has(.login-link), + &:has(.logged-in) { + display: block !important; + } + + // If logged-in is inside, ensure it displays properly + .logged-in { + display: flex !important; + } + } + + // Middle-fill spacer - keep visible + > span.middle-fill { + display: block !important; + flex: 1 1 auto; + } + + // Search component - keep visible and maintain flex behavior + > app-substance-text-search { + display: block !important; + flex-grow: 1; + max-width: 600px; + } + + // Classic view container - hide + .classic-view-container { + display: none !important; + } + } +} + +// ============================================================================ +// MAT-SIDENAV FIXES +// ============================================================================ + +.mat-drawer-container, +.mat-sidenav-container { + background-color: inherit; + color: inherit; +} + +.mat-drawer, +.mat-sidenav { + box-shadow: + 0px 8px 10px -5px rgba(0, 0, 0, 0.2), + 0px 16px 24px 2px rgba(0, 0, 0, 0.14), + 0px 6px 30px 5px rgba(0, 0, 0, 0.12); + background-color: white; +} + +.mat-drawer-backdrop, +.mat-sidenav-backdrop { + background-color: rgba(0, 0, 0, 0.6); +} + +.mat-drawer-content, +.mat-sidenav-content { + overflow: auto; + -webkit-overflow-scrolling: touch; +} + +// Fix sidenav positioning and transitions +.mat-drawer-side { + border-right: solid 1px rgba(0, 0, 0, 0.12); +} + +.mat-drawer.mat-drawer-side { + z-index: 2; +} + +// Global export button styles in sidenav content +mat-sidenav-content .controls-container .export-button { + color: var(--regular-black-color); + background-color: white; + border-radius: 4px; + box-shadow: + 0 3px 1px -2px rgba(0, 0, 0, 0.2), + 0 2px 2px rgba(0, 0, 0, 0.1411764706), + 0 1px 5px rgba(0, 0, 0, 0.1215686275); + padding: 16px 16px; +} + +button.mat-mdc-button.export-button > .mat-icon, +button.mat-mdc-button.export-button .mat-icon, +.mat-icon[data-mat-icon-name="chevron_down"] { + height: 24px !important; + width: 24px !important; + font-size: 24px !important; + line-height: 24px !important; +} + +// ============================================================================ +// GLOBAL BUTTON ICON SIZING + SPACING (MDC) +// ============================================================================ + +// Apply to "text buttons" (buttons that can have icon + label) +button.mat-mdc-button, +button.mat-mdc-raised-button, +button.mat-mdc-unelevated-button, +button.mat-mdc-outlined-button { + .mat-icon { + width: 24px; + height: 24px; + font-size: 24px; + line-height: 24px; + + // space between icon and label + margin-right: 6px; + margin-left: 0; + } + + // If svgIcon renders sizing on the SVG element + .mat-icon svg { + width: var(--button-icon-svg-size, 24px); + height: var(--button-icon-svg-size, 24px); + } +} + +// Do NOT add label spacing to icon-only buttons +button.mat-mdc-icon-button .mat-icon { + margin-right: 0; +} + +// ============================================================================ +// EXPORT BUTTON ONLY: keep svg mat-icon LEFT of label + spacing +// ============================================================================ + +button.export-button { + // Only for SVG icons inside export buttons + .mat-icon[data-mat-icon-type="svg"] { + order: 0 !important; + margin-right: 6px !important; + margin-left: 0 !important; + + width: 24px; + height: 24px; + + svg { + width: 24px; + height: 24px; + } + } + + // Ensure label stays after the icon + .mdc-button__label { + order: 1 !important; + display: inline-flex; + align-items: center; + } +} + +// button.mat-mdc-button >.mat-icon[data-mat-icon-name="chevron_down"]{ +// height: 24px !important; +// width: 24px !important; +// font-size: 24px !important; +// line-height: 24px !important; +// } + +// ============================================================================ +// GENERAL MDC FIXES +// ============================================================================ + +// Fix elevation classes if needed +.mat-elevation-z0 { + box-shadow: none; +} + +.mat-elevation-z1 { + box-shadow: + 0px 2px 1px -1px rgba(0, 0, 0, 0.2), + 0px 1px 1px 0px rgba(0, 0, 0, 0.14), + 0px 1px 3px 0px rgba(0, 0, 0, 0.12); +} + +.mat-elevation-z2 { + box-shadow: + 0px 3px 1px -2px rgba(0, 0, 0, 0.2), + 0px 2px 2px 0px rgba(0, 0, 0, 0.14), + 0px 1px 5px 0px rgba(0, 0, 0, 0.12); +} + +.mat-elevation-z3 { + box-shadow: + 0px 3px 3px -2px rgba(0, 0, 0, 0.2), + 0px 3px 4px 0px rgba(0, 0, 0, 0.14), + 0px 1px 8px 0px rgba(0, 0, 0, 0.12); +} + +.mat-elevation-z4 { + box-shadow: + 0px 2px 4px -1px rgba(0, 0, 0, 0.2), + 0px 4px 5px 0px rgba(0, 0, 0, 0.14), + 0px 1px 10px 0px rgba(0, 0, 0, 0.12); +} + +.mat-elevation-z6 { + box-shadow: + 0px 3px 5px -1px rgba(0, 0, 0, 0.2), + 0px 6px 10px 0px rgba(0, 0, 0, 0.14), + 0px 1px 18px 0px rgba(0, 0, 0, 0.12); +} + +.mat-elevation-z8 { + box-shadow: + 0px 5px 5px -3px rgba(0, 0, 0, 0.2), + 0px 8px 10px 1px rgba(0, 0, 0, 0.14), + 0px 3px 14px 2px rgba(0, 0, 0, 0.12); +} + +// Override MDC ripple behavior if it interferes with existing styles +.mat-mdc-button-ripple, +.mat-mdc-icon-button-ripple, +.mat-ripple, +.mat-mdc-button-persistent-ripple { + position: absolute; + pointer-events: none; +} + +// Ensure proper focus indicators +.mat-mdc-focus-indicator { + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + pointer-events: none; + border: 2px solid transparent; +} + +// Fix button touch targets that can cause layout issues +.mat-mdc-button-touch-target { + position: absolute; + top: 50%; + height: 48px; + left: 0; + right: 0; + transform: translateY(-50%); +} + +// ============================================================================ +// SPECIFIC COMPONENT FIXES +// ============================================================================ + +// Fix for search buttons in substance-text-search component +.search-button, +.close-button, +.activate-search-button { + &[mat-icon-button] { + // Ensure these buttons work properly with icon centering + .mat-icon { + display: inline-flex; + align-items: center; + justify-content: center; + } + } +} + +// Fix for login button +.login-link { + &[mat-button] { + display: inline-flex; + align-items: center; + justify-content: center; + } +} + +// Fix for user account buttons +.user-button { + &[mat-icon-button] { + // These have custom sizing, ensure icons still center + .mat-icon, + .user-icon { + display: inline-flex; + align-items: center; + justify-content: center; + } + } +} + +// ============================================================================ +// COMPREHENSIVE ICON CENTERING FIX +// ============================================================================ +// This ensures all mat-icons are properly centered regardless of context + +// Force all icon containers to use flexbox centering +button[mat-icon-button], +button[mat-mini-fab], +button[mat-fab], +a[mat-icon-button], +a[mat-mini-fab], +a[mat-fab], +.mat-icon-button, +.mat-mdc-icon-button, +.mat-mini-fab, +.mat-mdc-mini-fab, +.mat-fab, +.mat-mdc-fab { + display: inline-flex; + align-items: center !important; + justify-content: center !important; + + .mat-icon { + position: relative !important; + left: auto !important; + right: auto !important; + top: auto !important; + bottom: auto !important; + transform: none !important; + // Don't force margin to 0 - allow component-specific margins + } + + // Ensure SVG icons are centered within the mat-icon + .mat-icon svg, + svg { + position: relative; + left: auto; + right: auto; + top: auto; + bottom: auto; + transform: none; + display: block; + margin: auto; + } +} + +// Exception: substance-hierarchy tree toggle buttons — keep icon in static flow +.tree-button.mat-mdc-icon-button .mat-icon { + position: static !important; + top: auto !important; + left: auto !important; + right: auto !important; + bottom: auto !important; + margin: 0 !important; + transform: none !important; +} + +// Fix for icons in regular buttons (not icon buttons) +.mat-button, +.mat-raised-button, +.mat-flat-button, +.mat-stroked-button, +.mat-mdc-button, +.mat-mdc-raised-button, +.mat-mdc-unelevated-button, +.mat-mdc-outlined-button { + // Override Angular Material's DOM reordering + &:not([mat-icon-button]):not([mat-fab]):not([mat-mini-fab]) { + // CRITICAL: Angular Material wraps content in .mdc-button__label + // and places mat-icon OUTSIDE of it, but before it in the DOM + // We need to visually reorder them + + display: inline-flex !important; + flex-direction: row !important; + align-items: center !important; + + // The mat-icon appears FIRST in DOM (Angular moves it) + > .mat-icon { + order: 10 !important; // Force it to appear LAST visually + // margin-left: 8px !important; + margin-right: 0 !important; + vertical-align: middle; + display: inline-flex; + align-items: center; + justify-content: center; + + svg { + vertical-align: top; + } + } + + // The .mdc-button__label contains the text content + > .mdc-button__label { + order: 1 !important; // Make it appear FIRST visually + display: inline-flex; + align-items: center; + + // // If there's a mat-icon inside the label (shouldn't happen, but just in case) + // > .mat-icon { + // margin-left: 8px; + // order: inherit; + // } + } + + // Handle ripple and touch target (they should be positioned absolutely) + > .mat-mdc-button-persistent-ripple, + > .mat-mdc-button-ripple, + > .mat-mdc-button-touch-target, + > .mat-mdc-focus-indicator { + order: 0 !important; + position: absolute !important; + } + } +} + +// ============================================================================ +// EXPORT BUTTON: put SVG icon left, keep dropdown icon right +// (Matches the specificity of the global reorder rule) +// ============================================================================ + +.mat-mdc-button.export-button, +.mat-mdc-raised-button.export-button, +.mat-mdc-unelevated-button.export-button, +.mat-mdc-outlined-button.export-button { + &:not([mat-icon-button]):not([mat-fab]):not([mat-mini-fab]) { + display: inline-flex !important; + flex-direction: row !important; + align-items: center !important; + + // SVG icon (get_app) must be LEFT of label + > .mat-icon[data-mat-icon-type="svg"] { + order: 0 !important; + margin-right: 6px !important; + margin-left: 0 !important; + } + + // Label in the middle + > .mdc-button__label { + order: 1 !important; + } + + // Font icon (arrow_drop_down) stays on the RIGHT + > .mat-icon[data-mat-icon-type="font"] { + order: 2 !important; + margin-left: 6px !important; + margin-right: 0 !important; + } + } +} + +// ============================================================================ +// ANCHOR "BUTTONS" (a[mat-button], a[mat-flat-button], etc.) +// Restore icon sizing (24px) + spacing for svg icons inside anchors +// ============================================================================ + +a.mat-mdc-button, +a.mat-mdc-raised-button, +a.mat-mdc-unelevated-button, +a.mat-mdc-outlined-button { + &:not([mat-icon-button]):not([mat-fab]):not([mat-mini-fab]) { + display: inline-flex !important; + flex-direction: row !important; + align-items: center !important; + + // SVG icons should have proper size + spacing + > .mat-icon[data-mat-icon-type="svg"] { + order: 0 !important; + width: 24px !important; + height: 24px !important; + margin-right: 6px !important; + margin-left: 0 !important; + flex: 0 0 auto; + + svg { + width: 24px !important; + height: 24px !important; + } + } + + // Optional: keep label after icon (normal button behavior) + > .mdc-button__label { + order: 1 !important; + display: inline-flex; + align-items: center; + } + } +} + +// Fix for menu items with icons +.mat-menu-item, +.mat-mdc-menu-item { + .mat-icon { + margin-right: 16px; + vertical-align: middle; + } +} + +// Fix for list items with icons +.mat-list-item, +.mat-mdc-list-item { + .mat-icon { + margin-right: 16px; + flex-shrink: 0; + } +} + +.mat-mdc-form-field .mat-mdc-form-field-focus-overlay { + background: none !important; + box-shadow: none !important; +} + +.mat-mdc-tab-header { + margin-top: -10px; + border-bottom: 1px solid var(--grey-border-color); +} + +// ============================================================================ +// TEXTAREA FIXES +// ============================================================================ + +// Override the global align-items: center (correct for inputs, wrong for textareas) +// and reduce the excess padding-bottom on the infix for textarea form fields. +.mat-mdc-form-field .mdc-text-field--textarea { + .mat-mdc-form-field-flex { + align-items: flex-start; + } + + .mat-mdc-form-field-infix { + padding-bottom: 8px; + } +} + +// .mat-button, +// .matButton, +// .mat-mdc-button { +// color: var(--link-color) !important; +// } + +// .mat-mdc-button-disabled { +// color: #00000042 !important; +// } + +// ============================================================================ +// IMPURITIES FORM — custom-themed form fields inside .mat-form-field-style +// These were previously dead legacy selectors in impurities-substance-form.component.scss. +// CSS custom properties cascade through component boundaries without ::ng-deep. +// ============================================================================ + +.mat-form-field-style { + // Label color (unfocused) + --mdc-filled-text-field-label-text-color: var(--mat-form-field-label-color); + + // Active indicator (underline) + --mdc-filled-text-field-active-indicator-color: var( + --mat-form-field-underline-bg-color + ); + + // Focused states + --mdc-filled-text-field-focus-active-indicator-color: var( + --mat-form-field-focused-color + ); + --mdc-filled-text-field-focus-label-text-color: var( + --mat-form-field-focused-color + ); + + // Hint text color (validation hints shown in red) + mat-hint { + color: var(--regular-red-color) !important; + } + + // Disabled form fields + .mat-mdc-form-field-disabled, + mat-form-field.mat-form-field-disabled { + cursor: not-allowed; + * { + cursor: not-allowed; + } + } +} From a01b81fd8406528bd4346b1b15f32c64d957bbd3 Mon Sep 17 00:00:00 2001 From: Jaroslav Iha Date: Wed, 15 Apr 2026 16:42:07 +0200 Subject: [PATCH 370/408] refactor dropdown to mat-menu --- src/app/core/base/base.component.scss | 2 +- .../pfda-toolbar/pfda-toolbar.component.html | 22 +++++++---- .../pfda-toolbar/pfda-toolbar.component.scss | 39 ------------------- src/styles/_material-overrides.scss | 2 +- 4 files changed, 16 insertions(+), 49 deletions(-) diff --git a/src/app/core/base/base.component.scss b/src/app/core/base/base.component.scss index 35ff366e1..8dca2150c 100644 --- a/src/app/core/base/base.component.scss +++ b/src/app/core/base/base.component.scss @@ -111,7 +111,7 @@ .mat-toolbar { position: fixed; top: 0; - z-index: 1002; + z-index: 1001; } .logo { diff --git a/src/app/core/base/pfda-toolbar/pfda-toolbar.component.html b/src/app/core/base/pfda-toolbar/pfda-toolbar.component.html index 1a739eeac..fc475e439 100644 --- a/src/app/core/base/pfda-toolbar/pfda-toolbar.component.html +++ b/src/app/core/base/pfda-toolbar/pfda-toolbar.component.html @@ -17,21 +17,27 @@
    -
    + + + + diff --git a/src/app/core/base/pfda-toolbar/pfda-toolbar.component.scss b/src/app/core/base/pfda-toolbar/pfda-toolbar.component.scss index b24eacaba..8803f1450 100644 --- a/src/app/core/base/pfda-toolbar/pfda-toolbar.component.scss +++ b/src/app/core/base/pfda-toolbar/pfda-toolbar.component.scss @@ -89,46 +89,7 @@ $screenMedium: 1045px; } .gsrs-menu-trigger { - position: relative; display: inline-block; - - .gsrs-dropdown { - display: none; - position: absolute; - top: 100%; - left: 0; - z-index: 1002; - background: #fff; - min-width: 112px; - max-width: 280px; - border-radius: 4px; - box-shadow: 0px 2px 4px -1px rgba(0,0,0,0.2), 0px 4px 5px 0px rgba(0,0,0,0.14), 0px 1px 10px 0px rgba(0,0,0,0.12); - padding: 0; - - a { - display: flex; - align-items: center; - height: 48px; - padding: 0 16px; - color: rgba(0,0,0,0.87); - text-decoration: none; - white-space: nowrap; - font-family: Roboto, "Helvetica Neue", sans-serif; - font-size: 14px; - font-weight: 400; - line-height: 48px; - cursor: pointer; - box-sizing: border-box; - - &:hover { - background: rgba(0,0,0,0.04); - } - } - } - - &:hover .gsrs-dropdown { - display: block; - } } diff --git a/src/styles/_material-overrides.scss b/src/styles/_material-overrides.scss index dac5b3ac6..e270f08a0 100644 --- a/src/styles/_material-overrides.scss +++ b/src/styles/_material-overrides.scss @@ -1025,7 +1025,7 @@ app-loading .mat-mdc-progress-spinner { background-color: var(--primary-color); position: fixed; top: 0; - z-index: 1002; + z-index: 1001; // @media (max-width: 990px) { // height: auto; From 77575afdb71ba8535d1f96c096bd6730a3ba30ed Mon Sep 17 00:00:00 2001 From: Jaroslav Iha Date: Wed, 15 Apr 2026 19:08:25 +0200 Subject: [PATCH 371/408] revert --- .../pfda-toolbar/pfda-toolbar.component.html | 22 +- .../pfda-toolbar/pfda-toolbar.component.scss | 39 + src/styles/_material-overrides.scss | 1665 ----------------- 3 files changed, 47 insertions(+), 1679 deletions(-) delete mode 100644 src/styles/_material-overrides.scss diff --git a/src/app/core/base/pfda-toolbar/pfda-toolbar.component.html b/src/app/core/base/pfda-toolbar/pfda-toolbar.component.html index fc475e439..1a739eeac 100644 --- a/src/app/core/base/pfda-toolbar/pfda-toolbar.component.html +++ b/src/app/core/base/pfda-toolbar/pfda-toolbar.component.html @@ -17,27 +17,21 @@
    -
    + - - diff --git a/src/app/core/base/pfda-toolbar/pfda-toolbar.component.scss b/src/app/core/base/pfda-toolbar/pfda-toolbar.component.scss index 8803f1450..b24eacaba 100644 --- a/src/app/core/base/pfda-toolbar/pfda-toolbar.component.scss +++ b/src/app/core/base/pfda-toolbar/pfda-toolbar.component.scss @@ -89,7 +89,46 @@ $screenMedium: 1045px; } .gsrs-menu-trigger { + position: relative; display: inline-block; + + .gsrs-dropdown { + display: none; + position: absolute; + top: 100%; + left: 0; + z-index: 1002; + background: #fff; + min-width: 112px; + max-width: 280px; + border-radius: 4px; + box-shadow: 0px 2px 4px -1px rgba(0,0,0,0.2), 0px 4px 5px 0px rgba(0,0,0,0.14), 0px 1px 10px 0px rgba(0,0,0,0.12); + padding: 0; + + a { + display: flex; + align-items: center; + height: 48px; + padding: 0 16px; + color: rgba(0,0,0,0.87); + text-decoration: none; + white-space: nowrap; + font-family: Roboto, "Helvetica Neue", sans-serif; + font-size: 14px; + font-weight: 400; + line-height: 48px; + cursor: pointer; + box-sizing: border-box; + + &:hover { + background: rgba(0,0,0,0.04); + } + } + } + + &:hover .gsrs-dropdown { + display: block; + } } diff --git a/src/styles/_material-overrides.scss b/src/styles/_material-overrides.scss deleted file mode 100644 index e270f08a0..000000000 --- a/src/styles/_material-overrides.scss +++ /dev/null @@ -1,1665 +0,0 @@ -/** - * Global Angular Material Overrides - * - * Project-wide overrides for Angular Material (MDC) components. - * Loaded last in main.scss so these rules take precedence over Material's - * generated theme styles. - */ - -// ============================================================================ -// MAT-CARD FIXES -// ============================================================================ - -// Fix MDC card padding to match legacy behavior -.mat-mdc-card { - // Angular 15 MDC cards have padding: 16px by default - // Ensure consistency with Angular 14 legacy cards - padding: 16px; - margin: 0 auto 20px auto; - max-width: 1228px; - width: 100%; - box-sizing: border-box; - - &:not([class*="mat-elevation-z"]) { - box-shadow: - 0px 2px 1px -1px rgba(0, 0, 0, 0.2), - 0px 1px 1px 0px rgba(0, 0, 0, 0.14), - 0px 1px 3px 0px rgba(0, 0, 0, 0.12); - } -} - -// Fix MDC card header to match legacy -.mat-mdc-card-header { - display: flex; - padding: 0; -} - -// Fix MDC card title spacing -.mat-mdc-card-title { - font-size: 24px; - font-weight: 500; - margin-top: 0 !important; - margin-bottom: 0 !important; -} - -// Fix MDC card subtitle -.mat-mdc-card-subtitle { - margin-top: 0 !important; - margin-bottom: 12px !important; -} - -// Fix MDC card content spacing -.mat-mdc-card-content { - display: block; - - &:first-child { - padding-top: 0; - } - - &:last-child { - padding-bottom: 0; - } -} - -// ============================================================================ -// MAT-CHIP FIXES -// ============================================================================ - -// Fix chip styling for consistency with Angular 14 -.mat-mdc-chip { - &.mat-mdc-standard-chip { - min-height: 32px; - --mdc-chip-container-height: 32px; - } - - .mdc-evolution-chip__action--primary { - padding-left: 12px; - padding-right: 12px; - } - - .mdc-evolution-chip__text-label { - font-size: 14px; - } -} - -.mat-mdc-standard-chip:not(.mdc-evolution-chip--disabled) { - --mat-chip-elevated-container-color: var(--primary-color); - --mat-chip-label-text-color: #fff; /* optional */ -} - -// Chip spacing for both chip-set and chip-listbox (deprecated) -.mat-mdc-chip-set .mat-mdc-chip, -.mat-mdc-chip-listbox .mat-mdc-chip { - margin: 4px; -} - -// ============================================================================ -// MAT-FORM-FIELD FIXES -// ============================================================================ - -// MDC form fields have different default appearance and structure -.mat-mdc-form-field { - // Match Angular 14 form field appearance - font-family: Roboto, "Helvetica Neue", sans-serif !important; - // font-size: 14px !important; - // line-height: 1.125 !important; - - // Fix the wrapper to not add extra spacing - .mat-mdc-text-field-wrapper { - padding-bottom: 0; - background-color: transparent; - - .mat-mdc-form-field-flex { - align-items: center; - } - } - - // Fix fill appearance to match legacy - &.mat-form-field-appearance-fill { - .mat-mdc-text-field-wrapper { - padding-bottom: 0; - } - - .mdc-text-field { - background-color: transparent; - border-radius: 4px 4px 0 0; - // padding: 0 12px; - } - - .mdc-text-field--filled { - &:not(.mdc-text-field--disabled) { - background-color: transparent; - } - - .mdc-line-ripple::after { - border-bottom-width: 2px; - } - } - - // Fix infix padding - .mat-mdc-form-field-infix { - min-height: auto; - // padding: 25px 0 0.4375em 0; - } - - // Fix label positioning - .mat-mdc-floating-label { - top: 28px; - } - } - - // Fix outline appearance - &.mat-form-field-appearance-outline { - .mdc-text-field { - padding: 0; - } - - .mdc-text-field--outlined { - .mdc-notched-outline { - .mdc-notched-outline__leading, - .mdc-notched-outline__notch, - .mdc-notched-outline__trailing { - border-color: rgba(0, 0, 0, 0.38); - border-width: 1px; - } - } - - &:not(.mdc-text-field--disabled) { - &:hover .mdc-notched-outline { - .mdc-notched-outline__leading, - .mdc-notched-outline__notch, - .mdc-notched-outline__trailing { - border-color: rgba(0, 0, 0, 0.87); - } - } - } - } - - .mat-mdc-form-field-infix { - padding-top: 16px; - padding-bottom: 16px; - } - - .mat-mdc-floating-label { - top: 28px; - } - } - - // Fix input and label alignment - .mat-mdc-input-element { - font: inherit; - } - - // Fix label styling - .mat-mdc-floating-label { - font-size: 14px; - font-weight: 400; - } - - // Beat Material's runtime: .mdc-text-field--filled .mdc-floating-label { font-size: Xrem } - // That rule has specificity (0,2,0). Adding .mat-mdc-form-field parent gives us (0,3,0). - .mdc-text-field .mdc-floating-label { - font-size: var(--floating-label-font-size, 14px); - } - - // Fix subscript wrapper (hints and errors) - .mat-mdc-form-field-subscript-wrapper { - font-size: 12px; - margin-top: 0.66667em; - padding: 0; - - .mat-mdc-form-field-hint-wrapper, - .mat-mdc-form-field-error-wrapper { - padding: 0; - } - } - - // Fix bottom spacing - .mat-mdc-form-field-bottom-align::before { - content: none; - } - - // Fix icon button inside form field - .mat-mdc-icon-button { - width: 36px; - height: 36px; - - .mat-icon { - font-size: 20px; - width: 20px; - height: 20px; - } - } - - // Fix prefix and suffix icon alignment - .mat-mdc-form-field-icon-prefix, - .mat-mdc-form-field-icon-suffix { - display: inline-flex; - align-items: center; - justify-content: center; - - .mat-icon { - display: flex; - align-items: center; - justify-content: center; - } - } -} - -// Legacy form field icon fixes -.mat-form-field-prefix, -.mat-form-field-suffix { - .mat-icon, - .mat-icon-button { - display: inline-flex; - align-items: center; - justify-content: center; - } -} - -// ============================================================================ -// MAT-BUTTON FIXES -// ============================================================================ - -// Fix button styling for consistency with Angular 14 -.mat-mdc-button .mdc-button__label, -.mat-mdc-raised-button .mdc-button__label, -.mat-mdc-unelevated-button .mdc-button__label, -.mat-mdc-outlined-button .mdc-button__label { - white-space: nowrap; -} -.mat-mdc-button, -.mat-mdc-raised-button, -.mat-mdc-unelevated-button, -.mat-mdc-outlined-button { - // Match Angular 14 button heights and appearance - --mdc-text-button-container-height: 36px; - --mdc-filled-button-container-height: 36px; - --mdc-outlined-button-container-height: 36px; - --mdc-protected-button-container-height: 36px; - - font-family: Roboto, "Helvetica Neue", sans-serif; - font-size: 14px; - font-weight: 500; - line-height: 36px; - min-width: 64px; - padding: 0 16px; - - // CRITICAL: Ensure button content respects DOM order - display: inline-flex; - flex-direction: row; - align-items: center; - - // .mdc-button__label { - // font-size: 16px; - // font-weight: 500; - // line-height: normal; - // display: flex; - // flex-direction: row; - // align-items: center; - // order: 0; // Ensure label stays in natural order - // } - - // Ensure mat-icons stay in their HTML order - .mat-icon { - order: 0; // Don't reorder - } - - // Fix the persistent ripple that can cause visual issues - .mat-mdc-button-persistent-ripple { - border-radius: 4px; - } - - // Ensure touch target doesn't break layout - .mat-mdc-button-touch-target { - height: 100%; - } -} - -// Fix raised button elevation -.mat-mdc-raised-button:not(:disabled) { - box-shadow: - 0px 3px 1px -2px rgba(0, 0, 0, 0.2), - 0px 2px 2px 0px rgba(0, 0, 0, 0.14), - 0px 1px 5px 0px rgba(0, 0, 0, 0.12); - - &:hover { - box-shadow: - 0px 2px 4px -1px rgba(0, 0, 0, 0.2), - 0px 4px 5px 0px rgba(0, 0, 0, 0.14), - 0px 1px 10px 0px rgba(0, 0, 0, 0.12); - } -} - -// Fix flat button (now called unelevated in MDC) -.mat-mdc-unelevated-button { - --mdc-filled-button-container-height: 36px; -} - -// Ensure icon buttons have consistent size -.mat-mdc-icon-button { - --mdc-icon-button-state-layer-size: 40px; - width: 40px; - height: 40px; - padding: 8px; - line-height: 24px; - - .mat-mdc-button-touch-target { - width: 48px; - height: 48px; - } - - .mat-icon { - width: 24px; - height: 24px; - font-size: 24px; - line-height: 24px; - } -} - -// Fix FAB button -.mat-mdc-fab, -.mat-mdc-mini-fab { - .mat-mdc-button-touch-target { - width: 100%; - height: 100%; - } -} - -// ============================================================================ -// MAT-ICON FIXES -// ============================================================================ - -// Fix icon sizing and alignment -.mat-icon { - width: 24px; - height: 24px; - font-size: 24px; - line-height: 24px; - display: inline-flex; - align-items: center; - justify-content: center; - vertical-align: middle; - flex-shrink: 0; - - // Critical: Fix SVG positioning to center - svg { - width: 100%; - height: 100%; - fill: currentColor; - display: block; - margin: auto; - } -} - -// Specific fix for mat-icons inside buttons -button .mat-icon, -.mat-button .mat-icon, -.mat-raised-button .mat-icon, -.mat-flat-button .mat-icon, -.mat-stroked-button .mat-icon, -.mat-mdc-button .mat-icon, -.mat-mdc-raised-button .mat-icon, -.mat-mdc-unelevated-button .mat-icon, -.mat-mdc-outlined-button .mat-icon { - display: inline-flex; - align-items: center; - justify-content: center; - vertical-align: middle; -} - -// Fix icon button sizing (already covered above but ensure consistency) -.mat-mdc-icon-button { - display: inline-flex; - align-items: center; - justify-content: center; - - .mat-icon { - position: relative; - left: 0; - right: 0; - top: 0; - bottom: 0; - margin: auto; - } - - .mat-mdc-button-touch-target { - position: absolute; - } -} - -.mat-icon-button { - display: inline-flex; - align-items: center; - justify-content: center; - - .mat-icon { - position: relative; - margin: auto; - } -} - -// ============================================================================ -// MAT-MENU FIXES -// ============================================================================ - -// Fix menu panel -.mat-mdc-menu-panel { - min-width: 112px; - max-width: 280px; - border-radius: 4px; - box-shadow: - 0px 2px 4px -1px rgba(0, 0, 0, 0.2), - 0px 4px 5px 0px rgba(0, 0, 0, 0.14), - 0px 1px 10px 0px rgba(0, 0, 0, 0.12); -} - -.mat-mdc-menu-content { - padding: 0; - width: 100%; - box-sizing: border-box; - - // Ensure all menu items have consistent vertical rhythm - > * { - margin: 0 !important; - display: block !important; - } - - // Fix for wrapper divs (common pattern but not recommended) - > div { - display: contents !important; - margin: 0 !important; - padding: 0 !important; - height: auto !important; - } -} - -// Fix menu item styling -.mat-mdc-menu-item { - font-family: Roboto, "Helvetica Neue", sans-serif !important; - font-size: 14px !important; - font-weight: 400 !important; - min-height: 48px !important; - height: 48px !important; - padding: 0 16px !important; - display: flex !important; - align-items: center !important; - position: relative !important; - pointer-events: auto !important; - cursor: pointer !important; - width: 100% !important; - box-sizing: border-box !important; - margin: 0 !important; - line-height: 48px !important; - - .mat-icon { - margin-right: 16px; - line-height: normal !important; - } - - .mat-mdc-menu-item-text { - flex-grow: 1; - line-height: normal !important; - } - - // Ensure the MDC button inside menu item is clickable - .mat-mdc-menu-item-text, - .mdc-list-item__primary-text { - pointer-events: auto !important; - line-height: normal !important; - } - - // Fix for anchor tag menu items - &[href] { - pointer-events: auto !important; - cursor: pointer !important; - } - - // Fix internal content wrapper that Angular Material adds - .mdc-list-item__content { - display: flex !important; - align-items: center !important; - height: 48px !important; - padding: 0 !important; - margin: 0 !important; - } -} - -// Additional fixes for submenu trigger menu items (menu items with matMenuTriggerFor) -// The base .mat-mdc-menu-item rule above handles most of it, but ensure no overrides -.mat-mdc-menu-item.mat-mdc-menu-trigger, -a.mat-mdc-menu-item[ng-reflect-menu], -button.mat-mdc-menu-item[ng-reflect-menu], -.mat-mdc-menu-item.cdk-menu-trigger { - // Ensure submenu triggers don't have any extra spacing - vertical-align: middle !important; - - // Ensure the submenu indicator icon aligns properly - &::after { - line-height: normal !important; - } -} - -// Fix for nested menu positioning in Angular Material 19 MDC -// Ensure the CDK overlay positioning works correctly -.cdk-overlay-connected-position-bounding-box { - // Don't interfere with the calculated position - .mat-mdc-menu-panel { - // Ensure nested menus align properly with their trigger - &.mat-mdc-menu-nested { - margin-top: 0 !important; - } - } -} - -// ============================================================================ -// MAT-LIST FIXES -// ============================================================================ - -.mat-mdc-list, -.mat-mdc-list-base { - padding: 8px 0; - font-family: Roboto, "Helvetica Neue", sans-serif; -} - -.mat-mdc-list-item { - font-size: 14px; - font-weight: 400; - height: 48px; - - .mdc-list-item__primary-text { - font-size: 14px; - font-weight: 400; - color: var(--link-color); - } - - .mdc-list-item__secondary-text { - font-size: 12px; - font-weight: 400; - } - - // Prevent mat-icons from inheriting link-color from primary text - .mat-icon { - color: var(--mat-list-color); - } -} - -.mat-mdc-list-item-avatar { - width: 40px; - height: 40px; - border-radius: 50%; - margin-right: 16px; -} - -.mat-mdc-list-item-icon { - width: 24px; - height: 24px; - font-size: 24px; - margin-right: 16px; -} - -// Two-line list items -.mat-mdc-list-item.mdc-list-item--with-two-lines { - height: 64px; -} - -// Three-line list items -.mat-mdc-list-item.mdc-list-item--with-three-lines { - height: 88px; -} - -// ============================================================================ -// MAT-TABLE FIXES -// ============================================================================ - -// Fix table styling for consistency with Angular 14 -.mat-mdc-table { - background-color: inherit; - font-family: Roboto, "Helvetica Neue", sans-serif; -} - -.mat-mdc-header-row { - min-height: 56px; -} - -.mat-mdc-row { - min-height: 48px; -} - -.mat-mdc-header-cell { - font-size: 12px; - font-weight: 500; - color: rgba(0, 0, 0, 0.54); -} - -.mat-mdc-cell { - font-size: 14px; - color: rgba(0, 0, 0, 0.87); -} - -// ============================================================================ -// MAT-PAGINATOR FIXES -// ============================================================================ - -// Fix paginator styling to match Angular 14 -.mat-mdc-paginator { - background-color: transparent; - display: block; - font-family: Roboto, "Helvetica Neue", sans-serif; -} - -.mat-mdc-paginator-container { - display: flex; - align-items: center; - justify-content: flex-end; - min-height: 56px; - padding: 0 8px; -} - -.mat-mdc-paginator-page-size { - display: flex; - align-items: center; -} - -.mat-mdc-paginator-range-label { - margin: 0 32px 0 24px; -} - -.mat-mdc-paginator-page-size .mdc-notched-outline__leading, -.mat-mdc-paginator-page-size .mdc-notched-outline__trailing, -.mat-mdc-paginator-page-size .mdc-notched-outline__notch { - border-bottom: 1px solid currentColor !important; - border-radius: 0 !important; -} - -/* Adjust the overall infix height if needed, as padding affects height */ -.mat-mdc-paginator-page-size .mat-mdc-form-field-infix { - padding: 5px 0 !important; - min-height: auto !important; /* Ensure min-height does not enforce extra space */ -} - -/* Adjust the select's value text container height/line-height for vertical alignment */ -.mat-mdc-paginator-page-size .mat-mdc-select-value-text { - line-height: unset !important; - display: flex; - align-items: center; -} - -// ============================================================================ -// PAGE SELECTOR (GLOBAL) -// ============================================================================ - -// Page selector styles used across browse components -.page-selector { - display: flex; - flex-direction: row; - align-items: center; - margin-left: 20px; -} - -.page-label { - color: var(--dark-label-color); - display: block; - font-family: Roboto, "Helvetica Neue", sans-serif; - font-size: 14px; - padding-right: 12px; -} - -// Page selector form field adjustments -// Increase specificity to override the general mat-form-field-appearance-fill styles above -.page-selector > .mat-mdc-form-field.mat-form-field-appearance-fill { - .mat-mdc-form-field-infix { - min-height: auto; - padding: 10px 0 0 0 !important; - } - - .mdc-text-field { - padding: 0; - } -} - -// Responsive behavior - hide page selector on small screens -@media (max-width: 730px) { - .page-selector { - display: none !important; - } -} - -// ============================================================================ -// MAT-SELECT FIXES -// ============================================================================ - -// Fix select styling to match Angular 14 -.mat-mdc-select { - font-family: Roboto, "Helvetica Neue", sans-serif; - font-size: 14px; -} - -.mat-mdc-select-value { - font-size: 14px; -} - -.mat-mdc-select-trigger { - height: auto; -} - -.mat-mdc-select-panel { - max-height: 256px; -} - -.mat-mdc-option { - font-family: Roboto, "Helvetica Neue", sans-serif; - font-size: 14px; - min-height: 48px; - - .mdc-list-item__primary-text { - font-size: 14px !important; - } -} - -// ============================================================================ -// MAT-CHECKBOX FIXES -// ============================================================================ - -.mat-mdc-checkbox { - --mdc-checkbox-state-layer-size: 40px; - - .mdc-checkbox { - padding: calc((var(--mdc-checkbox-state-layer-size) - 18px) / 2); - } - - .mdc-checkbox__background { - width: 18px; - height: 18px; - top: calc((var(--mdc-checkbox-state-layer-size) - 18px) / 2); - left: calc((var(--mdc-checkbox-state-layer-size) - 18px) / 2); - } - - .mdc-form-field { - font-size: 14px; - } -} - -// ============================================================================ -// MAT-RADIO FIXES -// ============================================================================ - -.mat-mdc-radio-button { - .mdc-radio { - padding: 10px; - } - - .mdc-form-field { - font-size: 14px; - } -} - -// ============================================================================ -// MAT-EXPANSION-PANEL FIXES -// ============================================================================ - -.mat-expansion-panel { - box-shadow: - 0px 2px 1px -1px rgba(0, 0, 0, 0.2), - 0px 1px 1px 0px rgba(0, 0, 0, 0.14), - 0px 1px 3px 0px rgba(0, 0, 0, 0.12); -} - -.mat-expansion-panel-header { - font-family: Roboto, "Helvetica Neue", sans-serif; - font-size: 14px; - height: 48px; -} - -.mat-expansion-panel-content { - font-family: Roboto, "Helvetica Neue", sans-serif; -} - -// ============================================================================ -// MAT-DIALOG FIXES -// ============================================================================ - -.mat-mdc-dialog-container { - --mat-dialog-supporting-text-color: black; - - .mdc-dialog__surface { - border-radius: 4px; - padding: 24px; - } -} - -// User Edit dialog -.user-edit-dialog { - .mat-mdc-dialog-title { - font-size: 40px !important; - font-weight: bold !important; - } - - .mat-mdc-dialog-content { - padding: 10px 24px !important; - max-height: none !important; - overflow-y: unset !important; - } - - .mat-mdc-dialog-container, - .mdc-dialog__surface { - height: unset !important; - max-height: 90vh !important; - overflow-y: auto !important; - } -} - -// Cross Entity Search dialog — MDC caps surface at 560px by default; override per panel class -.cross-entity-search-dialog { - .mdc-dialog__surface { - max-width: none !important; - } -} - -// Advanced Selector dialog — override MDC's 560px max-width CSS variable at the pane level, -// which is where var(--mat-dialog-container-max-width, 560px) is resolved. Children that use -// max-width: inherit will then inherit none instead of 560px. -.advanced-selector-dialog { - --mat-dialog-container-max-width: none; - - .mat-mdc-dialog-inner-container, - .mdc-dialog__surface { - max-width: none !important; - } -} - -.mat-mdc-dialog-title { - font-size: 20px; - font-weight: 500; - margin: 0 0 16px; - padding: 24px 24px 0; -} - -.mat-mdc-dialog-content { - font-size: 14px; - padding: 0 24px; -} - -.mat-mdc-dialog-inner-container { - height: fit-content !important; - max-height: 90vh !important; - overflow-y: auto !important; -} - -.mat-mdc-dialog-actions { - padding: 0 24px 8px 24px !important; - min-height: 52px; -} - -// ============================================================================ -// MAT-TAB FIXES -// ============================================================================ - -.mat-mdc-tab-group { - font-family: Roboto, "Helvetica Neue", sans-serif; -} - -.mat-mdc-tab-list { - flex-grow: 0 !important; -} - -.mat-mdc-tab { - font-size: 14px; - font-weight: 500; - min-width: 160px; - height: 48px; -} - -.mat-mdc-tab-list .mat-mdc-tab, -.mat-tab-list .mat-tab-label { - letter-spacing: normal; - - .mdc-tab__text-label { - letter-spacing: normal; - line-height: 1.3; - } -} - -.mat-mdc-tab-body-content { - padding: 16px 0; -} - -// Admin tab group: 18px tab labels (beats Material runtime at 0,2,0) -.tab-group .mat-mdc-tab .mdc-tab__text-label { - font-size: 18px; -} - -// ============================================================================ -// MAT-SLIDER FIXES -// ============================================================================ - -.mat-mdc-slider { - .mdc-slider__track { - height: 2px; - } - - .mdc-slider__thumb-knob { - width: 12px; - height: 12px; - } -} - -// ============================================================================ -// MAT-PROGRESS-BAR FIXES -// ============================================================================ - -.mat-mdc-progress-bar { - --mdc-linear-progress-track-height: 4px; -} - -// ============================================================================ -// MAT-PROGRESS-SPINNER FIXES -// ============================================================================ - -.mat-mdc-progress-spinner { - circle { - stroke-width: 10%; - } -} - -// Scoped to app-loading only — prevents ALL spinners from being fixed/centered -app-loading .mat-mdc-progress-spinner { - position: fixed; - top: 50%; - left: 50%; - transform: translate(-50%, -50%); - z-index: 1003; - display: block; -} - -// ============================================================================ -// MAT-TOOLTIP FIXES -// ============================================================================ - -.mat-mdc-tooltip { - .mdc-tooltip__surface { - font-size: 10px; - padding: 4px 8px; - max-width: 200px; - } -} - -// ============================================================================ -// MAT-SNACKBAR FIXES -// ============================================================================ - -.mat-mdc-snack-bar-container { - .mdc-snackbar__surface { - background-color: #323232; - } - - .mdc-snackbar__label { - color: white; - font-size: 14px; - } -} - -// ============================================================================ -// MAT-TOOLBAR FIXES -// ============================================================================ - -.mat-toolbar, -.mat-mdc-toolbar { - font-family: Roboto, "Helvetica Neue", sans-serif; - font-size: 14px; - font-weight: 400; - display: flex; - box-sizing: border-box; - width: 100%; - flex-direction: row; - align-items: center; - white-space: nowrap; - padding: 0 16px; - min-height: 64px; - position: relative; - - // Single row toolbar (default) - &:not(.mat-toolbar-multiple-rows) { - flex-direction: row; - height: 64px; - background-color: var(--primary-color); - position: fixed; - top: 0; - z-index: 1001; - - // @media (max-width: 990px) { - // height: auto; - // flex-wrap: wrap; - // } - } - - // Multiple rows toolbar - &.mat-toolbar-multiple-rows { - flex-direction: column; - min-height: 64px; - } -} - -// Fix toolbar row heights to match Angular 14 -.mat-toolbar-row, -.mat-mdc-toolbar-row { - display: flex; - box-sizing: border-box; - width: 100%; - height: 64px; - flex-direction: row; - align-items: center; - white-space: nowrap; - padding: 0 16px; -} - -.mat-toolbar-single-row, -.mat-mdc-toolbar-single-row { - display: flex; - box-sizing: border-box; - width: 100%; - height: 64px; - flex-direction: row; - align-items: center; - white-space: nowrap; - padding: 0 16px; -} - -// Fix spacer/middle-fill pattern (flex-grow to push items to sides) -.mat-toolbar .middle-fill, -.mat-mdc-toolbar .middle-fill, -.mat-toolbar .spacer, -.mat-mdc-toolbar .spacer, -.mat-toolbar .fill, -.mat-mdc-toolbar .fill { - flex: 1 1 auto; -} - -// ============================================================================ -// RESPONSIVE TOOLBAR ADJUSTMENTS -// ============================================================================ - -// Tablet/Mobile: Hide most navigation buttons at 1350px, show only Logo, Menu, Search, and Login -// Note: scoped to :not(.pfda-toolbar) to avoid hiding pfda-toolbar buttons -@media (max-width: 1350px) { - .mat-toolbar:not(.pfda-toolbar), - .mat-mdc-toolbar:not(.pfda-toolbar) { - // Logo container - always visible - > .logo-container { - display: flex !important; - } - - // Menu button (nav-small span) - always visible - > span.nav-small { - display: inline-block !important; - } - - // Hide all div containers EXCEPT logo-container and the one containing login - > div { - // Hide by default - display: none !important; - - // But keep logo container visible - &.logo-container { - display: flex !important; - } - - // Keep the div that contains login button or logged-in user visible - &:has(.login-link), - &:has(.logged-in) { - display: block !important; - } - - // If logged-in is inside, ensure it displays properly - .logged-in { - display: flex !important; - } - } - - // Middle-fill spacer - keep visible - > span.middle-fill { - display: block !important; - flex: 1 1 auto; - } - - // Search component - keep visible and maintain flex behavior - > app-substance-text-search { - display: block !important; - flex-grow: 1; - max-width: 600px; - } - - // Classic view container - hide - .classic-view-container { - display: none !important; - } - } -} - -// ============================================================================ -// MAT-SIDENAV FIXES -// ============================================================================ - -.mat-drawer-container, -.mat-sidenav-container { - background-color: inherit; - color: inherit; -} - -.mat-drawer, -.mat-sidenav { - box-shadow: - 0px 8px 10px -5px rgba(0, 0, 0, 0.2), - 0px 16px 24px 2px rgba(0, 0, 0, 0.14), - 0px 6px 30px 5px rgba(0, 0, 0, 0.12); - background-color: white; -} - -.mat-drawer-backdrop, -.mat-sidenav-backdrop { - background-color: rgba(0, 0, 0, 0.6); -} - -.mat-drawer-content, -.mat-sidenav-content { - overflow: auto; - -webkit-overflow-scrolling: touch; -} - -// Fix sidenav positioning and transitions -.mat-drawer-side { - border-right: solid 1px rgba(0, 0, 0, 0.12); -} - -.mat-drawer.mat-drawer-side { - z-index: 2; -} - -// Global export button styles in sidenav content -mat-sidenav-content .controls-container .export-button { - color: var(--regular-black-color); - background-color: white; - border-radius: 4px; - box-shadow: - 0 3px 1px -2px rgba(0, 0, 0, 0.2), - 0 2px 2px rgba(0, 0, 0, 0.1411764706), - 0 1px 5px rgba(0, 0, 0, 0.1215686275); - padding: 16px 16px; -} - -button.mat-mdc-button.export-button > .mat-icon, -button.mat-mdc-button.export-button .mat-icon, -.mat-icon[data-mat-icon-name="chevron_down"] { - height: 24px !important; - width: 24px !important; - font-size: 24px !important; - line-height: 24px !important; -} - -// ============================================================================ -// GLOBAL BUTTON ICON SIZING + SPACING (MDC) -// ============================================================================ - -// Apply to "text buttons" (buttons that can have icon + label) -button.mat-mdc-button, -button.mat-mdc-raised-button, -button.mat-mdc-unelevated-button, -button.mat-mdc-outlined-button { - .mat-icon { - width: 24px; - height: 24px; - font-size: 24px; - line-height: 24px; - - // space between icon and label - margin-right: 6px; - margin-left: 0; - } - - // If svgIcon renders sizing on the SVG element - .mat-icon svg { - width: var(--button-icon-svg-size, 24px); - height: var(--button-icon-svg-size, 24px); - } -} - -// Do NOT add label spacing to icon-only buttons -button.mat-mdc-icon-button .mat-icon { - margin-right: 0; -} - -// ============================================================================ -// EXPORT BUTTON ONLY: keep svg mat-icon LEFT of label + spacing -// ============================================================================ - -button.export-button { - // Only for SVG icons inside export buttons - .mat-icon[data-mat-icon-type="svg"] { - order: 0 !important; - margin-right: 6px !important; - margin-left: 0 !important; - - width: 24px; - height: 24px; - - svg { - width: 24px; - height: 24px; - } - } - - // Ensure label stays after the icon - .mdc-button__label { - order: 1 !important; - display: inline-flex; - align-items: center; - } -} - -// button.mat-mdc-button >.mat-icon[data-mat-icon-name="chevron_down"]{ -// height: 24px !important; -// width: 24px !important; -// font-size: 24px !important; -// line-height: 24px !important; -// } - -// ============================================================================ -// GENERAL MDC FIXES -// ============================================================================ - -// Fix elevation classes if needed -.mat-elevation-z0 { - box-shadow: none; -} - -.mat-elevation-z1 { - box-shadow: - 0px 2px 1px -1px rgba(0, 0, 0, 0.2), - 0px 1px 1px 0px rgba(0, 0, 0, 0.14), - 0px 1px 3px 0px rgba(0, 0, 0, 0.12); -} - -.mat-elevation-z2 { - box-shadow: - 0px 3px 1px -2px rgba(0, 0, 0, 0.2), - 0px 2px 2px 0px rgba(0, 0, 0, 0.14), - 0px 1px 5px 0px rgba(0, 0, 0, 0.12); -} - -.mat-elevation-z3 { - box-shadow: - 0px 3px 3px -2px rgba(0, 0, 0, 0.2), - 0px 3px 4px 0px rgba(0, 0, 0, 0.14), - 0px 1px 8px 0px rgba(0, 0, 0, 0.12); -} - -.mat-elevation-z4 { - box-shadow: - 0px 2px 4px -1px rgba(0, 0, 0, 0.2), - 0px 4px 5px 0px rgba(0, 0, 0, 0.14), - 0px 1px 10px 0px rgba(0, 0, 0, 0.12); -} - -.mat-elevation-z6 { - box-shadow: - 0px 3px 5px -1px rgba(0, 0, 0, 0.2), - 0px 6px 10px 0px rgba(0, 0, 0, 0.14), - 0px 1px 18px 0px rgba(0, 0, 0, 0.12); -} - -.mat-elevation-z8 { - box-shadow: - 0px 5px 5px -3px rgba(0, 0, 0, 0.2), - 0px 8px 10px 1px rgba(0, 0, 0, 0.14), - 0px 3px 14px 2px rgba(0, 0, 0, 0.12); -} - -// Override MDC ripple behavior if it interferes with existing styles -.mat-mdc-button-ripple, -.mat-mdc-icon-button-ripple, -.mat-ripple, -.mat-mdc-button-persistent-ripple { - position: absolute; - pointer-events: none; -} - -// Ensure proper focus indicators -.mat-mdc-focus-indicator { - position: absolute; - top: 0; - left: 0; - right: 0; - bottom: 0; - pointer-events: none; - border: 2px solid transparent; -} - -// Fix button touch targets that can cause layout issues -.mat-mdc-button-touch-target { - position: absolute; - top: 50%; - height: 48px; - left: 0; - right: 0; - transform: translateY(-50%); -} - -// ============================================================================ -// SPECIFIC COMPONENT FIXES -// ============================================================================ - -// Fix for search buttons in substance-text-search component -.search-button, -.close-button, -.activate-search-button { - &[mat-icon-button] { - // Ensure these buttons work properly with icon centering - .mat-icon { - display: inline-flex; - align-items: center; - justify-content: center; - } - } -} - -// Fix for login button -.login-link { - &[mat-button] { - display: inline-flex; - align-items: center; - justify-content: center; - } -} - -// Fix for user account buttons -.user-button { - &[mat-icon-button] { - // These have custom sizing, ensure icons still center - .mat-icon, - .user-icon { - display: inline-flex; - align-items: center; - justify-content: center; - } - } -} - -// ============================================================================ -// COMPREHENSIVE ICON CENTERING FIX -// ============================================================================ -// This ensures all mat-icons are properly centered regardless of context - -// Force all icon containers to use flexbox centering -button[mat-icon-button], -button[mat-mini-fab], -button[mat-fab], -a[mat-icon-button], -a[mat-mini-fab], -a[mat-fab], -.mat-icon-button, -.mat-mdc-icon-button, -.mat-mini-fab, -.mat-mdc-mini-fab, -.mat-fab, -.mat-mdc-fab { - display: inline-flex; - align-items: center !important; - justify-content: center !important; - - .mat-icon { - position: relative !important; - left: auto !important; - right: auto !important; - top: auto !important; - bottom: auto !important; - transform: none !important; - // Don't force margin to 0 - allow component-specific margins - } - - // Ensure SVG icons are centered within the mat-icon - .mat-icon svg, - svg { - position: relative; - left: auto; - right: auto; - top: auto; - bottom: auto; - transform: none; - display: block; - margin: auto; - } -} - -// Exception: substance-hierarchy tree toggle buttons — keep icon in static flow -.tree-button.mat-mdc-icon-button .mat-icon { - position: static !important; - top: auto !important; - left: auto !important; - right: auto !important; - bottom: auto !important; - margin: 0 !important; - transform: none !important; -} - -// Fix for icons in regular buttons (not icon buttons) -.mat-button, -.mat-raised-button, -.mat-flat-button, -.mat-stroked-button, -.mat-mdc-button, -.mat-mdc-raised-button, -.mat-mdc-unelevated-button, -.mat-mdc-outlined-button { - // Override Angular Material's DOM reordering - &:not([mat-icon-button]):not([mat-fab]):not([mat-mini-fab]) { - // CRITICAL: Angular Material wraps content in .mdc-button__label - // and places mat-icon OUTSIDE of it, but before it in the DOM - // We need to visually reorder them - - display: inline-flex !important; - flex-direction: row !important; - align-items: center !important; - - // The mat-icon appears FIRST in DOM (Angular moves it) - > .mat-icon { - order: 10 !important; // Force it to appear LAST visually - // margin-left: 8px !important; - margin-right: 0 !important; - vertical-align: middle; - display: inline-flex; - align-items: center; - justify-content: center; - - svg { - vertical-align: top; - } - } - - // The .mdc-button__label contains the text content - > .mdc-button__label { - order: 1 !important; // Make it appear FIRST visually - display: inline-flex; - align-items: center; - - // // If there's a mat-icon inside the label (shouldn't happen, but just in case) - // > .mat-icon { - // margin-left: 8px; - // order: inherit; - // } - } - - // Handle ripple and touch target (they should be positioned absolutely) - > .mat-mdc-button-persistent-ripple, - > .mat-mdc-button-ripple, - > .mat-mdc-button-touch-target, - > .mat-mdc-focus-indicator { - order: 0 !important; - position: absolute !important; - } - } -} - -// ============================================================================ -// EXPORT BUTTON: put SVG icon left, keep dropdown icon right -// (Matches the specificity of the global reorder rule) -// ============================================================================ - -.mat-mdc-button.export-button, -.mat-mdc-raised-button.export-button, -.mat-mdc-unelevated-button.export-button, -.mat-mdc-outlined-button.export-button { - &:not([mat-icon-button]):not([mat-fab]):not([mat-mini-fab]) { - display: inline-flex !important; - flex-direction: row !important; - align-items: center !important; - - // SVG icon (get_app) must be LEFT of label - > .mat-icon[data-mat-icon-type="svg"] { - order: 0 !important; - margin-right: 6px !important; - margin-left: 0 !important; - } - - // Label in the middle - > .mdc-button__label { - order: 1 !important; - } - - // Font icon (arrow_drop_down) stays on the RIGHT - > .mat-icon[data-mat-icon-type="font"] { - order: 2 !important; - margin-left: 6px !important; - margin-right: 0 !important; - } - } -} - -// ============================================================================ -// ANCHOR "BUTTONS" (a[mat-button], a[mat-flat-button], etc.) -// Restore icon sizing (24px) + spacing for svg icons inside anchors -// ============================================================================ - -a.mat-mdc-button, -a.mat-mdc-raised-button, -a.mat-mdc-unelevated-button, -a.mat-mdc-outlined-button { - &:not([mat-icon-button]):not([mat-fab]):not([mat-mini-fab]) { - display: inline-flex !important; - flex-direction: row !important; - align-items: center !important; - - // SVG icons should have proper size + spacing - > .mat-icon[data-mat-icon-type="svg"] { - order: 0 !important; - width: 24px !important; - height: 24px !important; - margin-right: 6px !important; - margin-left: 0 !important; - flex: 0 0 auto; - - svg { - width: 24px !important; - height: 24px !important; - } - } - - // Optional: keep label after icon (normal button behavior) - > .mdc-button__label { - order: 1 !important; - display: inline-flex; - align-items: center; - } - } -} - -// Fix for menu items with icons -.mat-menu-item, -.mat-mdc-menu-item { - .mat-icon { - margin-right: 16px; - vertical-align: middle; - } -} - -// Fix for list items with icons -.mat-list-item, -.mat-mdc-list-item { - .mat-icon { - margin-right: 16px; - flex-shrink: 0; - } -} - -.mat-mdc-form-field .mat-mdc-form-field-focus-overlay { - background: none !important; - box-shadow: none !important; -} - -.mat-mdc-tab-header { - margin-top: -10px; - border-bottom: 1px solid var(--grey-border-color); -} - -// ============================================================================ -// TEXTAREA FIXES -// ============================================================================ - -// Override the global align-items: center (correct for inputs, wrong for textareas) -// and reduce the excess padding-bottom on the infix for textarea form fields. -.mat-mdc-form-field .mdc-text-field--textarea { - .mat-mdc-form-field-flex { - align-items: flex-start; - } - - .mat-mdc-form-field-infix { - padding-bottom: 8px; - } -} - -// .mat-button, -// .matButton, -// .mat-mdc-button { -// color: var(--link-color) !important; -// } - -// .mat-mdc-button-disabled { -// color: #00000042 !important; -// } - -// ============================================================================ -// IMPURITIES FORM — custom-themed form fields inside .mat-form-field-style -// These were previously dead legacy selectors in impurities-substance-form.component.scss. -// CSS custom properties cascade through component boundaries without ::ng-deep. -// ============================================================================ - -.mat-form-field-style { - // Label color (unfocused) - --mdc-filled-text-field-label-text-color: var(--mat-form-field-label-color); - - // Active indicator (underline) - --mdc-filled-text-field-active-indicator-color: var( - --mat-form-field-underline-bg-color - ); - - // Focused states - --mdc-filled-text-field-focus-active-indicator-color: var( - --mat-form-field-focused-color - ); - --mdc-filled-text-field-focus-label-text-color: var( - --mat-form-field-focused-color - ); - - // Hint text color (validation hints shown in red) - mat-hint { - color: var(--regular-red-color) !important; - } - - // Disabled form fields - .mat-mdc-form-field-disabled, - mat-form-field.mat-form-field-disabled { - cursor: not-allowed; - * { - cursor: not-allowed; - } - } -} From 5ef17877a4aa743317accf780ec2ae3a678a8c66 Mon Sep 17 00:00:00 2001 From: Jaroslav Iha Date: Wed, 15 Apr 2026 19:45:24 +0200 Subject: [PATCH 372/408] update z-index of pfda toolbar --- src/app/core/base/pfda-toolbar/pfda-toolbar.component.scss | 1 + 1 file changed, 1 insertion(+) diff --git a/src/app/core/base/pfda-toolbar/pfda-toolbar.component.scss b/src/app/core/base/pfda-toolbar/pfda-toolbar.component.scss index b24eacaba..7bd619c6e 100644 --- a/src/app/core/base/pfda-toolbar/pfda-toolbar.component.scss +++ b/src/app/core/base/pfda-toolbar/pfda-toolbar.component.scss @@ -18,6 +18,7 @@ $screenMedium: 1045px; line-height: 16px; padding: 0 8px; height: auto; + z-index: 1002; a { color: inherit; From 4948e21803992128b92d5fd3e936cf622277625c Mon Sep 17 00:00:00 2001 From: Iaroslav Moskviak Date: Fri, 17 Apr 2026 00:10:34 -0400 Subject: [PATCH 373/408] Toolbar responsive fixes & subunit textarea improvements --- src/app/core/base/base.component.html | 8 +- src/app/core/base/base.component.scss | 28 +-- src/app/core/base/base.component.ts | 11 + .../subunit-form/subunit-form.component.html | 191 +++++++++++++----- .../subunit-form/subunit-form.component.scss | 6 + .../subunit-form/subunit-form.component.ts | 9 +- src/styles/_material-overrides.scss | 5 + 7 files changed, 176 insertions(+), 82 deletions(-) diff --git a/src/app/core/base/base.component.html b/src/app/core/base/base.component.html index b0d7f404c..8ca67ecda 100644 --- a/src/app/core/base/base.component.html +++ b/src/app/core/base/base.component.html @@ -117,7 +117,7 @@ Browse Substances
    -
    +
    @@ -360,7 +360,7 @@ Registrars @@ -370,7 +370,7 @@
    -
    +
    { @@ -141,6 +145,13 @@ export class BaseComponent implements OnInit, OnDestroy { } async ngOnInit() { + const breakpointSub = this.breakpointObserver + .observe(['(min-width: 1611px)', '(min-width: 1501px)']) + .subscribe(result => { + this.showRegistrars = result.breakpoints['(min-width: 1611px)']; + this.showBrowseOther = result.breakpoints['(min-width: 1501px)']; + }); + this.subscriptions.push(breakpointSub); this.showHeaderBar = this.activatedRoute.snapshot.queryParams["header"] || "true"; this.loadedComponents = this.configService.configData.loadedComponents || null; diff --git a/src/app/core/substance-form/subunit-form/subunit-form.component.html b/src/app/core/substance-form/subunit-form/subunit-form.component.html index 4df2cc912..d90354cca 100644 --- a/src/app/core/substance-form/subunit-form/subunit-form.component.html +++ b/src/app/core/substance-form/subunit-form/subunit-form.component.html @@ -1,72 +1,163 @@ -
    +
    Deleted  -
    -
    +
    -
    -
    Subunit {{subunit.subunitIndex}}
    -
    -
    - -
    -
    -
    -
    -
    {{subunit.sequence}}
    -
    +
    +
    +
    +
    +
    +
    {{ subunit.sequence }}
    +
    +
    - -
    -
    -
    -
    -
    {{num[1]}}
    -
    - - {{subunit.unitValue}} - +
    +
    +
    +
    +
    {{ num[1] }}
    +
    + + {{ subunit.unitValue }} + +
    -
    -
    -
    - Generate{{sequenceType? 'd':''}} links and sugars for this subunit as a - - +
    +
    + Generate{{ sequenceType ? "d" : "" }} links and sugars for this subunit as + a + + sequence
    - +
    diff --git a/src/app/core/substance-form/subunit-form/subunit-form.component.scss b/src/app/core/substance-form/subunit-form/subunit-form.component.scss index ebeb2841c..66842eb3f 100644 --- a/src/app/core/substance-form/subunit-form/subunit-form.component.scss +++ b/src/app/core/substance-form/subunit-form/subunit-form.component.scss @@ -11,6 +11,11 @@ .sequence-textarea { font-size: 14px; letter-spacing: 2px; + font-family: monospace; + overflow: hidden; + resize: vertical; + width: 100%; + box-sizing: border-box; } .show { @@ -66,6 +71,7 @@ .section-units-container { display: flex; + min-width: calc(10 * 15.5px); .section-unit { /*flex-grow: 1;*/ diff --git a/src/app/core/substance-form/subunit-form/subunit-form.component.ts b/src/app/core/substance-form/subunit-form/subunit-form.component.ts index 4744b14ef..c6e3e9c87 100644 --- a/src/app/core/substance-form/subunit-form/subunit-form.component.ts +++ b/src/app/core/substance-form/subunit-form/subunit-form.component.ts @@ -258,11 +258,18 @@ window.open( url, '_blank'); setTimeout(() => { const textArea = document.getElementsByClassName('sequence-textarea'); [].forEach.call(textArea, function (area) { - area.style.height = (area.scrollHeight + 10) + 'px'; + area.style.height = 'auto'; + area.style.height = area.scrollHeight + 'px'; }); }); } } + autoResize(event: Event): void { + const textarea = event.target as HTMLTextAreaElement; + textarea.style.height = 'auto'; + textarea.style.height = textarea.scrollHeight + 'px'; + } + change(event): void { if (this.toggle[this.subunit.subunitIndex] === false) { event.target.value = this.subunit.sequence; diff --git a/src/styles/_material-overrides.scss b/src/styles/_material-overrides.scss index 59a87649b..5edd9a79a 100644 --- a/src/styles/_material-overrides.scss +++ b/src/styles/_material-overrides.scss @@ -540,6 +540,11 @@ button.mat-mdc-menu-item[ng-reflect-menu], } } +// Ensure CDK overlay (tooltips, menus, dialogs) always renders above the fixed toolbar (z-index: 1001) +.cdk-overlay-container { + z-index: 1002; +} + // Fix for nested menu positioning in Angular Material 19 MDC // Ensure the CDK overlay positioning works correctly .cdk-overlay-connected-position-bounding-box { From 3a0020dbe4c114ba8c2e273197b39d48142dd8df Mon Sep 17 00:00:00 2001 From: Iaroslav Moskviak Date: Fri, 17 Apr 2026 10:06:53 -0400 Subject: [PATCH 374/408] fixed specificity issue for font-family --- .../substance-form/subunit-form/subunit-form.component.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/core/substance-form/subunit-form/subunit-form.component.scss b/src/app/core/substance-form/subunit-form/subunit-form.component.scss index 66842eb3f..43ad44bc4 100644 --- a/src/app/core/substance-form/subunit-form/subunit-form.component.scss +++ b/src/app/core/substance-form/subunit-form/subunit-form.component.scss @@ -8,7 +8,7 @@ } } -.sequence-textarea { +textarea.sequence-textarea { font-size: 14px; letter-spacing: 2px; font-family: monospace; From 06a4fa84c40816267fa3a5c1f07630ec6b7b716c Mon Sep 17 00:00:00 2001 From: Iaroslav Moskviak Date: Fri, 17 Apr 2026 16:25:49 -0400 Subject: [PATCH 375/408] Query Role UI Fixes & Alignment Display Improvements --- .../disulfide-links-form.component.html | 1 + .../disulfide-links-form.component.scss | 8 +++----- .../sequence-alignment.component.html | 8 +++++--- .../sequence-alignment.component.scss | 19 +++++++++++++++++-- .../sequence-alignment.component.ts | 11 ++++++++++- .../clinical-trials-browse.component.ts | 15 ++++++--------- 6 files changed, 42 insertions(+), 20 deletions(-) diff --git a/src/app/core/substance-form/disulfide-links/disulfide-links-form.component.html b/src/app/core/substance-form/disulfide-links/disulfide-links-form.component.html index af6d7582e..5005660a4 100644 --- a/src/app/core/substance-form/disulfide-links/disulfide-links-form.component.html +++ b/src/app/core/substance-form/disulfide-links/disulfide-links-form.component.html @@ -19,6 +19,7 @@ [compareWith]="compareSites" (selectionChange)="updateSuggestions($event.value, index)" class="site-select" + [panelWidth]="null" > {{ cys.subunitIndex }}_{{ cys.residueIndex }} diff --git a/src/app/core/substance-form/disulfide-links/disulfide-links-form.component.scss b/src/app/core/substance-form/disulfide-links/disulfide-links-form.component.scss index abf786ca2..879a4f69f 100644 --- a/src/app/core/substance-form/disulfide-links/disulfide-links-form.component.scss +++ b/src/app/core/substance-form/disulfide-links/disulfide-links-form.component.scss @@ -29,14 +29,13 @@ } .site { - max-width:80px; + max-width: 80px; } - sites { - width:35%; + width: 35%; } - /* .sites{ + /* .sites{ flex-grow: 1; padding-right: 15px; width:40%; @@ -44,5 +43,4 @@ .site-select { } - } diff --git a/src/app/core/substances-browse/sequence-alignment/sequence-alignment.component.html b/src/app/core/substances-browse/sequence-alignment/sequence-alignment.component.html index 99f36af27..02b88d47d 100644 --- a/src/app/core/substances-browse/sequence-alignment/sequence-alignment.component.html +++ b/src/app/core/substances-browse/sequence-alignment/sequence-alignment.component.html @@ -3,8 +3,10 @@ Subunit {{alignmentArray.subunitIndex}} {{alignmentArray.id}}
    -
    -
    -  
    +
    +
    
    +    
    
    +    
    
    +  
    diff --git a/src/app/core/substances-browse/sequence-alignment/sequence-alignment.component.scss b/src/app/core/substances-browse/sequence-alignment/sequence-alignment.component.scss index 5db1e86a3..9bbda44ff 100644 --- a/src/app/core/substances-browse/sequence-alignment/sequence-alignment.component.scss +++ b/src/app/core/substances-browse/sequence-alignment/sequence-alignment.component.scss @@ -1,12 +1,27 @@ .alignment-input { - overflow-x: auto; - overflow-y: auto; border-radius: 4px; border: 1px solid var(--sub-hierarchy-odd-bg-color); margin-top: 8px; padding: 5px; padding-bottom: 10px; max-height: 250px; + overflow-y: auto; +} + +.alignment-stats, +.alignment-target-sites, +.alignment-body { + margin: 0; +} + +.alignment-target-sites { + white-space: pre-wrap; + padding-left: 14ch; + text-indent: -14ch; +} + +.alignment-body { + overflow-x: auto; } .alignment-container { diff --git a/src/app/core/substances-browse/sequence-alignment/sequence-alignment.component.ts b/src/app/core/substances-browse/sequence-alignment/sequence-alignment.component.ts index 1ffaac19c..ca06392b7 100644 --- a/src/app/core/substances-browse/sequence-alignment/sequence-alignment.component.ts +++ b/src/app/core/substances-browse/sequence-alignment/sequence-alignment.component.ts @@ -11,6 +11,8 @@ export class SequenceAlignmentComponent implements OnInit { @Input() alignmentArray: Alignment; alignment: Alignment; text: string; + targetSitesText: string; + alignmentBodyText: string; constructor() { } ngOnInit() { @@ -30,7 +32,14 @@ export class SequenceAlignmentComponent implements OnInit { this.text += 'matched: = ' + this.alignment.score.toString() + ' \n'; } if (this.alignment.score) { - this.text += this.alignment.alignment; + const alignStr = this.alignment.alignment || ''; + const targetMatch = alignStr.match(/^(Target Sites:[^\n]*\n?)([\s\S]*)$/m); + if (targetMatch) { + this.targetSitesText = targetMatch[1]; + this.alignmentBodyText = targetMatch[2]; + } else { + this.alignmentBodyText = alignStr; + } } } } diff --git a/src/app/fda/clinical-trials/clinical-trials-browse/clinical-trials-browse.component.ts b/src/app/fda/clinical-trials/clinical-trials-browse/clinical-trials-browse.component.ts index f259d302b..19791f9ad 100644 --- a/src/app/fda/clinical-trials/clinical-trials-browse/clinical-trials-browse.component.ts +++ b/src/app/fda/clinical-trials/clinical-trials-browse/clinical-trials-browse.component.ts @@ -63,6 +63,7 @@ export class ClinicalTrialsBrowseComponent implements OnInit, AfterViewInit, OnD private subscriptions: Array = []; dataSource = new MatTableDataSource([]); canDelete: boolean = false; + canEdit: boolean = false; showExactMatches = false; private isComponentInit = false; @@ -114,16 +115,12 @@ export class ClinicalTrialsBrowseComponent implements OnInit, AfterViewInit, OnD } this.overlayContainer = this.overlayContainerService.getContainerElement(); + this.canEdit = await this.authService.hasSpecificPrivilege('Edit'); this.canDelete = await this.authService.hasSpecificPrivilege('Delete Lower Level Items'); - const authSubscription = this.authService.getAuth().subscribe(auth => { - - // testing - if (this.canDelete) { - this.displayedColumns = ['edit', 'trialNumber', 'title', 'lastUpdated', 'delete']; - } else { - this.displayedColumns = ['edit', 'trialNumber', 'title', 'lastUpdated']; - } - }); + const columns = ['trialNumber', 'title', 'lastUpdated']; + if (this.canEdit) { columns.unshift('edit'); } + if (this.canDelete) { columns.push('delete'); } + this.displayedColumns = columns; this.searchTypes = [ {'title': 'All', 'value': 'all'}, {'title': 'Title', 'value': 'title'}, From c7883888011f192ca770b3287ffca3e7c6f12350 Mon Sep 17 00:00:00 2001 From: Iaroslav Moskviak Date: Mon, 20 Apr 2026 15:29:19 -0400 Subject: [PATCH 376/408] miscl fixes --- .../user-edit-dialog.component.ts | 18 ++++++++++++++++-- .../core/registrars/registrars.component.html | 2 +- .../clinical-trials-browse.component.html | 6 +++--- .../clinical-trials-browse.component.ts | 3 +-- 4 files changed, 21 insertions(+), 8 deletions(-) diff --git a/src/app/core/admin/user-management/user-edit-dialog/user-edit-dialog.component.ts b/src/app/core/admin/user-management/user-edit-dialog/user-edit-dialog.component.ts index 1ed593e71..723ae247f 100644 --- a/src/app/core/admin/user-management/user-edit-dialog/user-edit-dialog.component.ts +++ b/src/app/core/admin/user-management/user-edit-dialog/user-edit-dialog.component.ts @@ -161,8 +161,17 @@ export class UserEditDialogComponent implements OnInit { }); } + private isValidEmail(email: string): boolean { + if (!email || email.trim() === '') return true; // email is optional + return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email.trim()); + } + saveChanges(): void { - if (this.changePassword && this.newPassword !== '' ) { + if (this.user.user.email && !this.isValidEmail(this.user.user.email)) { + this.isError = true; + this.message = 'Email format is incorrect'; + return; + } else if (this.changePassword && this.newPassword !== '' ) { this.isError = true; this.message = 'Cancel or submit new password to save other changes'; } else if(!this.selectedRole || this.selectedRole===null || this.selectedRole.length ===0){ @@ -225,13 +234,18 @@ export class UserEditDialogComponent implements OnInit { this.message = 'Unable to edit user'; if (error.error) { this.isError = true; - this.message = error; + this.message = error.error.message || error.message || 'Unable to edit user'; } }); } addUser(): void { this.isError = false; + if (this.user.user.email && !this.isValidEmail(this.user.user.email)) { + this.isError = true; + this.message = 'Email format is incorrect'; + return; + } if (this.newPassword === this.newPasswordConfirm) { if(!this.selectedRole || this.selectedRole===null || this.selectedRole.length ===0){ this.message = "Please select a role for this user"; diff --git a/src/app/core/registrars/registrars.component.html b/src/app/core/registrars/registrars.component.html index 326c208d2..5636cf8be 100644 --- a/src/app/core/registrars/registrars.component.html +++ b/src/app/core/registrars/registrars.component.html @@ -297,7 +297,7 @@

    + [queryParams]="{facets: link.queryParams}">
    {{link.display}} diff --git a/src/app/fda/clinical-trials/clinical-trials-browse/clinical-trials-browse.component.html b/src/app/fda/clinical-trials/clinical-trials-browse/clinical-trials-browse.component.html index 049beaf4c..5d7f32afc 100644 --- a/src/app/fda/clinical-trials/clinical-trials-browse/clinical-trials-browse.component.html +++ b/src/app/fda/clinical-trials/clinical-trials-browse/clinical-trials-browse.component.html @@ -124,14 +124,14 @@

    Browse Clinical Trials

    Edit{{ canEdit ? 'Edit' : 'View' }} + diff --git a/src/app/fda/clinical-trials/clinical-trials-browse/clinical-trials-browse.component.ts b/src/app/fda/clinical-trials/clinical-trials-browse/clinical-trials-browse.component.ts index 19791f9ad..99402835f 100644 --- a/src/app/fda/clinical-trials/clinical-trials-browse/clinical-trials-browse.component.ts +++ b/src/app/fda/clinical-trials/clinical-trials-browse/clinical-trials-browse.component.ts @@ -117,8 +117,7 @@ export class ClinicalTrialsBrowseComponent implements OnInit, AfterViewInit, OnD this.overlayContainer = this.overlayContainerService.getContainerElement(); this.canEdit = await this.authService.hasSpecificPrivilege('Edit'); this.canDelete = await this.authService.hasSpecificPrivilege('Delete Lower Level Items'); - const columns = ['trialNumber', 'title', 'lastUpdated']; - if (this.canEdit) { columns.unshift('edit'); } + const columns = ['edit', 'trialNumber', 'title', 'lastUpdated']; if (this.canDelete) { columns.push('delete'); } this.displayedColumns = columns; this.searchTypes = [ From 5a0e2984b05ad14d276ac4051935673bcc51c91f Mon Sep 17 00:00:00 2001 From: Iaroslav Moskviak Date: Mon, 20 Apr 2026 21:56:21 -0400 Subject: [PATCH 377/408] registrar name change fix --- src/app/core/base/base.component.html | 4 ++-- src/app/core/base/base.component.scss | 7 +++++++ 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/src/app/core/base/base.component.html b/src/app/core/base/base.component.html index 20140b812..c7dcaf3e4 100644 --- a/src/app/core/base/base.component.html +++ b/src/app/core/base/base.component.html @@ -364,7 +364,7 @@ *ngIf="canRegister && !showRegistrars" mat-menu-item > - Registrars + Registrar Dashboard
    diff --git a/src/app/core/base/base.component.scss b/src/app/core/base/base.component.scss index afa884459..8830cf897 100644 --- a/src/app/core/base/base.component.scss +++ b/src/app/core/base/base.component.scss @@ -218,6 +218,13 @@ button.mat-mdc-button.top-button { padding-left: 15px; } +.registrar-dashboard-label { + font-size: 16px; + line-height: 1.2; + text-align: center; + display: inline-block; +} + // @media(max-width: $nav-breaking-point) { // .nav-big { // display: none; From 7a21d8c7f3f85e561cafbff27a899f2019aec7dd Mon Sep 17 00:00:00 2001 From: Jaroslav Iha Date: Wed, 22 Apr 2026 13:11:06 +0200 Subject: [PATCH 378/408] fix: export delete before cancel --- .../download-monitor/download-monitor.component.html | 2 +- .../download-monitor/download-monitor.component.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/app/core/auth/user-downloads/download-monitor/download-monitor.component.html b/src/app/core/auth/user-downloads/download-monitor/download-monitor.component.html index 065e72d03..9b79e0faa 100644 --- a/src/app/core/auth/user-downloads/download-monitor/download-monitor.component.html +++ b/src/app/core/auth/user-downloads/download-monitor/download-monitor.component.html @@ -126,7 +126,7 @@
    - diff --git a/src/app/core/auth/user-downloads/download-monitor/download-monitor.component.ts b/src/app/core/auth/user-downloads/download-monitor/download-monitor.component.ts index 2cc8343f6..68bf17a19 100644 --- a/src/app/core/auth/user-downloads/download-monitor/download-monitor.component.ts +++ b/src/app/core/auth/user-downloads/download-monitor/download-monitor.component.ts @@ -79,7 +79,7 @@ export class DownloadMonitorComponent implements OnInit, OnDestroy { } deleteDownload() { - this.authService.deleteDownload(this.download.removeUrl.url).pipe(take(1)).subscribe(response => { + this.authService.deleteDownload(this.download.removeUrl?.url || this.download.cancelUrl.url.replace('/@cancel', '')).pipe(take(1)).subscribe(response => { this.deleted = true; }); } From af3bab49f3b11e225217542f8f1ebba549a390ed Mon Sep 17 00:00:00 2001 From: Jaroslav Iha Date: Tue, 28 Apr 2026 11:21:59 +0200 Subject: [PATCH 379/408] fix: add substance.version check to saveSubstance --- src/app/core/substance-form/substance-form.component.ts | 6 ++++-- src/app/core/substance/substance.service.ts | 9 ++++----- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/src/app/core/substance-form/substance-form.component.ts b/src/app/core/substance-form/substance-form.component.ts index 7fe1795b1..0b4857342 100644 --- a/src/app/core/substance-form/substance-form.component.ts +++ b/src/app/core/substance-form/substance-form.component.ts @@ -1467,8 +1467,10 @@ export class SubstanceFormComponent implements OnInit, AfterViewInit, AfterViewC const json = this.substanceFormService.cleanSubstance(); const time = new Date().getTime(); - this.substanceFormService.regenUUID(); - json.uuid = this.substanceFormService.cleanSubstance().uuid; + // Always generate a fresh UUID for the draft copy — do NOT mutate the live substance, + // otherwise the save logic will treat a new substance as an existing one (PUT vs POST). + // A new UUID on every save ensures no two drafts share the same UUID. + json.uuid = this.utilsService.newUUID(); const uuid = json.uuid ? json.uuid : 'register'; const type = json.substanceClass; diff --git a/src/app/core/substance/substance.service.ts b/src/app/core/substance/substance.service.ts index c0340a270..ebebbcbb2 100644 --- a/src/app/core/substance/substance.service.ts +++ b/src/app/core/substance/substance.service.ts @@ -887,7 +887,8 @@ export class SubstanceService extends BaseHttpService { delete (substance as any).$$tmpStructureId; } - const method = type === 'import' || !substance.uuid ? 'POST' : 'PUT'; + // Use POST for new substances (no uuid OR no version), PUT for existing ones + const method = type === 'import' || !substance.uuid || !substance.version ? 'POST' : 'PUT'; const options = { body: substance }; const url = `${this.apiBaseUrl}substances?view=internal`; @@ -915,10 +916,8 @@ export class SubstanceService extends BaseHttpService { saveSubstanceWithoutValidation(substance: SubstanceDetail, type?: string): Observable { const url = `${this.apiBaseUrl}substances/novalid?view=internal`; - let method = 'PUT'; - if (type && type === 'import') { - method = 'POST'; - } + // Use POST for imports or new substances (no uuid or no version) + let method = (type === 'import' || !substance.uuid || !substance.version) ? 'POST' : 'PUT'; const options = { body: substance }; From 400d2c6465558bad3e813da36151c2f887a37774 Mon Sep 17 00:00:00 2001 From: Iaroslav Moskviak Date: Mon, 4 May 2026 13:39:54 -0400 Subject: [PATCH 380/408] substance hierarchy fix plus mobile responsiveness touch up --- .../substance-details.component.scss | 285 ++++++++-------- .../substance-hierarchy.component.scss | 26 +- .../substance-hierarchy.component.ts | 51 +-- .../substance-text-search.component.scss | 9 + .../substance-hierarchy.component.scss | 24 ++ .../substance-hierarchy.component.ts | 1 + .../substance-summary-card.component.html | 2 +- .../substance-summary-card.component.scss | 315 ++++++++++-------- .../substances-browse.component.html | 2 + .../substances-browse.component.scss | 107 ++++-- .../substances-browse.component.ts | 2 + src/styles/_variables.scss | 10 +- 12 files changed, 512 insertions(+), 322 deletions(-) diff --git a/src/app/core/substance-details/substance-details.component.scss b/src/app/core/substance-details/substance-details.component.scss index 52db0ed65..3a206be6d 100644 --- a/src/app/core/substance-details/substance-details.component.scss +++ b/src/app/core/substance-details/substance-details.component.scss @@ -1,134 +1,151 @@ -.side-nav-content { - display: flex; - box-sizing: border-box; - width: 100%; - flex-direction: row; - align-items: center; -} - -.substance-details { - width: 100%; - max-width: 928px; - box-sizing: border-box; -} - -.title-container { - padding: 0 20px; - display: flex; - align-items: center; -} - -.title-card { - padding-top: 15px; - padding-bottom: 15px; - margin-bottom: 10px; -} - -.white-background { - background-color: var(--regular-white-color); -} - -// Target the actual mat-drawer element that Material measures -// Use !important to override Material's CSS variable defaults -mat-sidenav.substance-sidenav.mat-drawer { - width: 315px !important; - min-width: 315px !important; - max-width: 315px !important; - flex: 0 0 315px !important; - box-sizing: border-box; // CRITICAL: Include padding in width calculation -} - -// Legacy fallback (lower specificity) -.substance-sidenav { - width: 315px; - min-width: 315px; - max-width: 315px; - flex: 0 0 315px; - box-sizing: border-box; -} - -.substance-nav-list.mat-mdc-list-base { - display: flex !important; - flex-direction: column; -} - -:host ::ng-deep .substance-nav-list - .mat-mdc-list-item - .mdc-list-item__primary-text { - color: #000; -} - -.list-content { - display: flex; - flex-direction: row; - align-items: center; - box-sizing: border-box; - flex-wrap: nowrap; -} - -.substance-title { - display: inline-block; -} - -.capitalized { - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - display: block; - min-width: 0; -} - -.approvalID { - font-size: 20px; - color: var(--pink-span-color); - font-weight: 500; -} - -.approvalIDColor { - color: var(--pink-span-color); - margin-bottom: 0px !important; - margin-top: 0px !important; -} - -.chip { - display: inline-block; - background-color: var(--pale-border-color); - border-radius: 50%; - padding: 0 5px; - margin-left: 15px; -} - -mat-panel-title { - h2 { - text-transform: capitalize; - } -} - -::ng-deep .nav-chevron { - margin-right: 0 !important; - svg { - height: 24px; - width: 24px; - } -} - -@media (max-width: 1100px) { - .title-container { - padding-left: 25px; - } -} - -@media (max-width: 500px) { - .title-container { - h1 { - word-break: break-all; - } - } -} - -@media (max-width: 603px) { - ::ng-deep .mat-paginator-range-label { - margin-left: 10px; - margin-right: 10px; - } -} +@use "../../../styles/variables" as *; + +.side-nav-content { + display: flex; + box-sizing: border-box; + width: 100%; + flex-direction: row; + align-items: center; +} + +.substance-details { + width: 100%; + max-width: 928px; + box-sizing: border-box; +} + +.title-container { + padding: 0 20px; + display: flex; + align-items: center; +} + +.title-card { + padding-top: 15px; + padding-bottom: 15px; + margin-bottom: 10px; +} + +.white-background { + background-color: var(--regular-white-color); +} + +// Target the actual mat-drawer element that Material measures +// Use !important to override Material's CSS variable defaults +mat-sidenav.substance-sidenav.mat-drawer { + width: 315px !important; + min-width: 315px !important; + max-width: 315px !important; + flex: 0 0 315px !important; + box-sizing: border-box; +} + +// Legacy fallback (lower specificity) +.substance-sidenav { + width: 315px; + min-width: 315px; + max-width: 315px; + flex: 0 0 315px; + box-sizing: border-box; +} + +.substance-nav-list.mat-mdc-list-base { + display: flex !important; + flex-direction: column; +} + +:host ::ng-deep .substance-nav-list + .mat-mdc-list-item + .mdc-list-item__primary-text { + color: #000; +} + +.list-content { + display: flex; + flex-direction: row; + align-items: center; + box-sizing: border-box; + flex-wrap: nowrap; +} + +.substance-title { + display: inline-block; +} + +.capitalized { + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + display: block; + min-width: 0; +} + +.approvalID { + font-size: 20px; + color: var(--pink-span-color); + font-weight: 500; +} + +.approvalIDColor { + color: var(--pink-span-color); + margin-bottom: 0 !important; + margin-top: 0 !important; +} + +.chip { + display: inline-block; + background-color: var(--pale-border-color); + border-radius: 50%; + padding: 0 5px; + margin-left: 15px; +} + +mat-panel-title { + h2 { + text-transform: capitalize; + } +} + +::ng-deep .nav-chevron { + margin-right: 0 !important; + svg { + height: 24px; + width: 24px; + } +} + +// ─── Responsive ────────────────────────────────────────────────────────────── + +@media (max-width: $breakpoint-desktop-sm) { + .title-container { + padding-left: 25px; + } +} + +@media (max-width: $breakpoint-tablet) { + // Allow expansion panel headers to grow so long section titles wrap fully + ::ng-deep .mat-expansion-panel-header { + height: auto !important; + min-height: 48px; + padding-top: 8px; + padding-bottom: 8px; + } + + mat-panel-title h2 { + white-space: normal; + margin: 0; + } +} + +@media (max-width: $breakpoint-mobile) { + .title-container { + h1 { + word-break: break-all; + } + } + + ::ng-deep .mat-paginator-range-label { + margin-left: 10px; + margin-right: 10px; + } +} diff --git a/src/app/core/substance-details/substance-hierarchy/substance-hierarchy.component.scss b/src/app/core/substance-details/substance-hierarchy/substance-hierarchy.component.scss index 911b29241..270891607 100644 --- a/src/app/core/substance-details/substance-hierarchy/substance-hierarchy.component.scss +++ b/src/app/core/substance-details/substance-hierarchy/substance-hierarchy.component.scss @@ -1,3 +1,5 @@ +@use "../../../../styles/variables" as *; + .mat-tree-node { min-height:30px !important; } @@ -124,4 +126,26 @@ .current-record { font-weight:bold; } - + +@media (max-width: $breakpoint-tablet) { + .title { + flex: 1 1 0; + min-width: 0; + max-width: none; + } + + .hierarchy-link { + white-space: normal; + word-break: break-word; + } + + .right-container { + max-width: 120px; + flex-shrink: 0; + } + + .filler { + display: none; + } +} + diff --git a/src/app/core/substance-details/substance-hierarchy/substance-hierarchy.component.ts b/src/app/core/substance-details/substance-hierarchy/substance-hierarchy.component.ts index 889b6faf3..2d2ad9247 100644 --- a/src/app/core/substance-details/substance-hierarchy/substance-hierarchy.component.ts +++ b/src/app/core/substance-details/substance-hierarchy/substance-hierarchy.component.ts @@ -6,11 +6,11 @@ import { NestedTreeControl } from '@angular/cdk/tree'; import { MatTreeNestedDataSource } from '@angular/material/tree'; import { HierarchyNode } from '@gsrs-core/substances-browse/substance-hierarchy/hierarchy.model'; -@Component({ - selector: 'app-substance-hierarchy', - templateUrl: './substance-hierarchy.component.html', - styleUrls: ['./substance-hierarchy.component.scss'], - standalone: false +@Component({ + selector: 'app-substance-hierarchy', + templateUrl: './substance-hierarchy.component.html', + styleUrls: ['./substance-hierarchy.component.scss'], + standalone: false }) export class SubstanceHierarchyComponent extends SubstanceCardBase implements OnInit { @@ -22,19 +22,19 @@ export class SubstanceHierarchyComponent extends SubstanceCardBase implements On super(); } - uuid: string; - name: string; + uuid!: string; + name!: string; approvalID?: string; treeControl = new NestedTreeControl(node => node.children); dataSource = new MatTreeNestedDataSource(); - selfNode: HierarchyNode; - activeNode: any; + selfNode!: HierarchyNode; + activeNode!: any; canEdit: boolean = false; hasChild = (_: number, node: any) => !!node.children && node.children.length > 0; async ngOnInit() { - this.uuid = this.substance.uuid; - this.name = this.substance._nameHTML; + this.uuid = this.substance.uuid!; + this.name = this.substance._nameHTML! this.selfNode = { 'id': 0, 'type': 'ROOT', @@ -42,15 +42,15 @@ export class SubstanceHierarchyComponent extends SubstanceCardBase implements On 'expandable': false, 'value': { 'refuuid': this.uuid, + 'refPname': this.name, 'name': this.name, 'approvalID': this.approvalID || '' }, 'relationship': '' }; - this.substanceService.getHierarchy(this.uuid).subscribe(resp => { - this.loadHierarchy(resp); - }, error => { - this.loadHierarchy([this.selfNode]); + this.substanceService.getHierarchy(this.uuid).subscribe({ + next: resp => this.loadHierarchy(resp), + error: () => this.loadHierarchy([this.selfNode]) }); this.canEdit = await this.authService.hasSpecificPrivilege('Edit') } @@ -75,10 +75,10 @@ export class SubstanceHierarchyComponent extends SubstanceCardBase implements On this.activeNode = this.dataSource.data[0]; } - formatHierarchy(data: any): HierarchyNode { + formatHierarchy(data: any): HierarchyNode[] { let lastID = ''; let lastProp = ''; - const parentRemap = []; + const parentRemap: [number, number][] = []; for (let i = (data.length - 1); i >= 0; i--) { if (data[i].depth === 0) { @@ -113,7 +113,7 @@ export class SubstanceHierarchyComponent extends SubstanceCardBase implements On data[i].relationship += '{SUBCONCEPT} '; } else if (data[i].type.includes('IS G1SS CONSTITUENT OF')) { data[i].relationship += '{G1SS} '; - } else if ((data[i].type.length > 8 ) && (data[i].relationship = '')) { + } else if ((data[i].type.length > 8 ) && (data[i].relationship === '')) { data[i].relationship += ' {' + data[i].type + '} '; } @@ -123,7 +123,7 @@ export class SubstanceHierarchyComponent extends SubstanceCardBase implements On lastProp = data[i].type; } // further remove self referential relationships with both salt and moiety relationship. - data.sort(function(a, b) { + data.sort(function(a: any, b: any) { const textA = a.refuuid.toUpperCase(); const textB = b.refuuid.toUpperCase(); if (textA === textB) { @@ -149,7 +149,7 @@ export class SubstanceHierarchyComponent extends SubstanceCardBase implements On } } } - data.sort(function(a, b) { + data.sort(function(a: any, b: any) { return a.id - b.id; }); @@ -163,10 +163,11 @@ export class SubstanceHierarchyComponent extends SubstanceCardBase implements On return data; } - list_to_tree(list) { - const map = {}, roots = []; - let node, i; - for (i = 0; i < list.length; i += 1) { + list_to_tree(list: any[]): HierarchyNode[] { + const map: Record = {}; + const roots: HierarchyNode[] = []; + let node: any; + for (let i = 0; i < list.length; i += 1) { map[list[i].id] = i; list[i].children = []; if (i === 0) { @@ -178,7 +179,7 @@ export class SubstanceHierarchyComponent extends SubstanceCardBase implements On list[i].order = 'odd'; } } - for (i = 0; i < list.length; i += 1) { + for (let i = 0; i < list.length; i += 1) { node = list[i]; if (node.parent !== '#') { list[map[node.parent]].children.push(node); diff --git a/src/app/core/substance-text-search/substance-text-search.component.scss b/src/app/core/substance-text-search/substance-text-search.component.scss index e3fbf7d5f..529457eb9 100644 --- a/src/app/core/substance-text-search/substance-text-search.component.scss +++ b/src/app/core/substance-text-search/substance-text-search.component.scss @@ -240,6 +240,7 @@ form:not(.header) { .close-button { display: inline-block !important; width: auto; + padding-left: 0; } } } @@ -303,6 +304,14 @@ form:not(.header) { } } +@media (max-width: $breakpoint-mobile-sm) { + .active-search { + &.search-container { + padding-right: 90px; + } + } +} + @keyframes expandWidth { from { width: 0; diff --git a/src/app/core/substances-browse/substance-hierarchy/substance-hierarchy.component.scss b/src/app/core/substances-browse/substance-hierarchy/substance-hierarchy.component.scss index d565a5f8e..48cb32cce 100644 --- a/src/app/core/substances-browse/substance-hierarchy/substance-hierarchy.component.scss +++ b/src/app/core/substances-browse/substance-hierarchy/substance-hierarchy.component.scss @@ -1,3 +1,5 @@ +@use "../../../../styles/variables" as *; + .mat-tree-node { min-height:30px !important; } @@ -137,3 +139,25 @@ margin-bottom: 5px; .current-record { font-weight:bold; } + +@media (max-width: $breakpoint-tablet) { + .title { + flex: 1 1 0; + min-width: 0; + max-width: none; + } + + .hierarchy-link { + white-space: normal; + word-break: break-word; + } + + .right-container { + max-width: 120px; + flex-shrink: 0; + } + + .filler { + display: none; + } +} diff --git a/src/app/core/substances-browse/substance-hierarchy/substance-hierarchy.component.ts b/src/app/core/substances-browse/substance-hierarchy/substance-hierarchy.component.ts index be95d5ff1..abda8ca36 100644 --- a/src/app/core/substances-browse/substance-hierarchy/substance-hierarchy.component.ts +++ b/src/app/core/substances-browse/substance-hierarchy/substance-hierarchy.component.ts @@ -36,6 +36,7 @@ export class SubstanceHierarchyComponent implements OnInit { 'expandable': false, 'value': { 'refuuid': this.uuid, + 'refPname': this.name, 'name': this.name, 'approvalID': this.approvalID || '' }, diff --git a/src/app/core/substances-browse/substance-summary-card/substance-summary-card.component.html b/src/app/core/substances-browse/substance-summary-card/substance-summary-card.component.html index 0e0fbe0fa..b3376b332 100644 --- a/src/app/core/substances-browse/substance-summary-card/substance-summary-card.component.html +++ b/src/app/core/substances-browse/substance-summary-card/substance-summary-card.component.html @@ -316,7 +316,7 @@
    -
    +
    Names:
    diff --git a/src/app/core/substances-browse/substance-summary-card/substance-summary-card.component.scss b/src/app/core/substances-browse/substance-summary-card/substance-summary-card.component.scss index 274f40cc2..fc84ba18f 100644 --- a/src/app/core/substances-browse/substance-summary-card/substance-summary-card.component.scss +++ b/src/app/core/substances-browse/substance-summary-card/substance-summary-card.component.scss @@ -1,225 +1,262 @@ +@use "../../../../styles/variables" as *; + .select-list { --floating-label-font-size: 16px; } // Angular Material 15 MDC card support .mat-mdc-card { - max-width: 1228px; - margin-bottom: 20px; - width: 100%; - box-sizing: border-box; + max-width: 1228px; + margin-bottom: 20px; + width: 100%; + box-sizing: border-box; } .mat-card { - max-width: 1228px; - margin-bottom: 20px; + max-width: 1228px; + margin-bottom: 20px; } -.mat-card, .mat-mdc-card, .controls-container { - width: 100%; - box-sizing: border-box; +.mat-card, +.mat-mdc-card, +.controls-container { + width: 100%; + box-sizing: border-box; } .button-wrapper { - max-width: 170px; + max-width: 170px; + + @media (max-width: $breakpoint-tablet) { + max-width: none; + display: flex; + flex-wrap: nowrap; + margin-bottom: 20px; + } } .summary-button { - --mdc-icon-button-state-layer-size: 36px; - width: 36px; - height: 36px; + --mdc-icon-button-state-layer-size: 36px; + width: 36px; + height: 36px; } .mat-button { - background: transparent; - border: none; - cursor: pointer; + background: transparent; + border: none; + cursor: pointer; } .loading-label { - font-weight: bold; -min-width: 100px; + font-weight: bold; + min-width: 100px; } .spinner { - height: 24px; - width: 24px; - margin-left: 10px; + height: 24px; + width: 24px; + margin-left: 10px; margin-right: 10px; - margin-top: -5px; -margin-bottom: 5px; + margin-top: -5px; + margin-bottom: 5px; } - - .name-loading { - display: flex; - width: 300px; + display: flex; + width: 300px; } .show-more { - text-decoration: underline; - color: var(--link-primary-color); - cursor: pointer; + text-decoration: underline; + color: var(--link-primary-color); + cursor: pointer; } // Angular Material 15 MDC card title .mat-mdc-card-title { - display: flex; - box-sizing: border-box; - width: 100%; - flex-direction: row; - align-items: center; - white-space: normal; - - .substance-name { - color: var(--link-primary-color); - padding-right: 10px; - } + display: flex; + box-sizing: border-box; + width: 100%; + flex-direction: row; + align-items: center; + white-space: normal; + + .substance-name { + color: var(--link-primary-color); + padding-right: 10px; + } - .approval { - font-size: 16px; - color: var(--pink-span-color); - } + .approval { + font-size: 16px; + color: var(--pink-span-color); + } } .mat-card-title { - display: flex; - box-sizing: border-box; - width: 100%; - flex-direction: row; - align-items: center; - white-space: nowrap; + display: flex; + box-sizing: border-box; + width: 100%; + flex-direction: row; + align-items: center; + white-space: nowrap; } .moreLink { - color: var(--link-primary-color); - font-weight: 600; - padding-left: 10px; - font-size: 15px; + color: var(--link-primary-color); + font-weight: 600; + padding-left: 10px; + font-size: 15px; } .mat-card-title { - white-space: normal; - - .substance-name { - color: var(--link-primary-color); - padding-right: 10px; - } + white-space: normal; - .approval { - font-size: 16px; - color: var(--pink-span-color); - } + .substance-name { + color: var(--link-primary-color); + padding-right: 10px; + } + .approval { + font-size: 16px; + color: var(--pink-span-color); + } } .substance-content { - display: flex; - flex-direction: row; + display: flex; + flex-direction: row; } .tile .structure-container { - width:100%; - padding-right:0px; + width: 100%; + padding-right: 0px; } .structure-container { - padding-right: 10px; + padding-right: 10px; } // Angular Material 15 MDC card content .mat-mdc-card-content { - .mat-chip-list-container { - margin-left: -10px; - margin-bottom: 10px; - } + .mat-chip-list-container { + margin-left: -10px; + margin-bottom: 10px; + } } .mat-card-content { - - .mat-chip-list-container { - margin-left: -10px; - margin-bottom: 10px; - } + .mat-chip-list-container { + margin-left: -10px; + margin-bottom: 10px; + } } .tile .image-thumbnail { - margin:auto; - margin-bottom:20px; - height:175px; - width:175px; + margin: auto; + margin-bottom: 20px; + height: 175px; + width: 175px; } -.image-thumbnail{ - height:150px; - width:150px; +.image-thumbnail { + height: 150px; + width: 150px; } -.zoom:hover{ - cursor:zoom-in; +.zoom:hover { + cursor: zoom-in; } .substance-data { - display: flex; - flex-direction: row; + display: flex; + flex-direction: row; - &:not(:last-child) { - margin-bottom: 15px; - } + &:not(:last-child) { + margin-bottom: 15px; + } - .label { - font-weight: bold; - min-width: 100px; - } + .label { + font-weight: bold; + min-width: 100px; + } } -.icon-align{ - margin-top: -10px; - vertical-align: bottom; +.icon-align { + margin-top: -10px; + vertical-align: bottom; } -.ext-link{ - color: var(--link-primary-color); - text-decoration-style: unset; +.ext-link { + color: var(--link-primary-color); + text-decoration-style: unset; } -@media(max-width: 700px) { - .mat-card-title, - .mat-mdc-card-title { - flex-direction: column; - align-items: flex-start; +@media (max-width: $breakpoint-tablet) { + .mat-card-title, + .mat-mdc-card-title { + flex-direction: column; + align-items: flex-start; - .substance-name { - margin-bottom: 10px; - } + .substance-name { + margin-bottom: 10px; + } + } + + .substance-data { + flex-direction: column; + + .label { + margin-bottom: 10px; + } + + .value, + .code-system { + padding-left: 20px; } + } + .substance-info, + .right-aligned { .substance-data { - flex-direction: column; + flex-direction: row; - .label { - margin-bottom: 10px; - } + .label { + margin-bottom: 0; + } - .value, .code-system { - padding-left: 20px; - } + .value { + padding-left: 8px; + } } + } } .lock-icon { - color: var(--lock-icon-color); + color: var(--lock-icon-color); } -@media(max-width: 600px) { +@media (max-width: $breakpoint-mobile) { + .substance-content { + flex-direction: column; - .substance-content { - flex-direction: column; - - .image-thumbnail { - margin: 0 auto; - } + .image-thumbnail { + margin: 0 auto; } + } + + .substance-info { + text-align: left; + margin-left: 0; + width: 100%; + } + + .right-aligned { + text-align: left; + margin-left: 0; + } + + .inxight-container { + text-align: left; + } } .definition { @@ -228,7 +265,7 @@ margin-bottom: 5px; .similarity { padding-left: 10px; - font-family: Menlo,Monaco,Consolas,"Courier New",monospace; + font-family: Menlo, Monaco, Consolas, "Courier New", monospace; color: var(--pink-span-color); } @@ -237,25 +274,25 @@ margin-bottom: 5px; } .inxight-container { - text-align: right; - padding-bottom: 15px; + text-align: right; + padding-bottom: 15px; } .small-icon { - height:20px; - width: 20px; - padding-bottom: 5px; - vertical-align: middle; + height: 20px; + width: 20px; + padding-bottom: 5px; + vertical-align: middle; } .match-context { - display: block; - font-size: small; - color: var(--regular-green-color); - margin-top: 8px; - margin-bottom: 8px; + display: block; + font-size: small; + color: var(--regular-green-color); + margin-top: 8px; + margin-bottom: 8px; } ::ng-deep .mat-mdc-form-field-infix { - padding-bottom: 0; + padding-bottom: 0; } diff --git a/src/app/core/substances-browse/substances-browse.component.html b/src/app/core/substances-browse/substances-browse.component.html index 52fef9e0b..d0666a810 100644 --- a/src/app/core/substances-browse/substances-browse.component.html +++ b/src/app/core/substances-browse/substances-browse.component.html @@ -727,6 +727,7 @@ [pageSizeOptions]="[5, 10, 50, 100]" (page)="changePage($event)" [showFirstLastButtons]="true" + [hidePageSize]="isMobile" > @@ -1327,6 +1328,7 @@ [pageSize]="pageSize" [pageSizeOptions]="[5, 10, 50, 100]" (page)="changePage($event)" + [hidePageSize]="isMobile" >
    diff --git a/src/app/core/substances-browse/substances-browse.component.scss b/src/app/core/substances-browse/substances-browse.component.scss index afe80d759..c35b46c1a 100644 --- a/src/app/core/substances-browse/substances-browse.component.scss +++ b/src/app/core/substances-browse/substances-browse.component.scss @@ -1,3 +1,5 @@ +@use "../../../styles/variables" as *; + ::ng-deep .mat-expansion-panel-content > .mat-expansion-panel-body { padding: 0 12px 10px; } @@ -427,7 +429,7 @@ .menu-checkbox:hover { background-color: var(--regular-white-color); } -@media (max-width: 1750px) { +@media (max-width: $breakpoint-desktop-lg) { .full-paginator { width: 100%; align-content: center; @@ -447,7 +449,7 @@ } } -@media (max-width: 1100px) { +@media (max-width: $breakpoint-desktop-sm) { .controls-container, .search-parameters { padding-left: 30px; @@ -466,7 +468,7 @@ } } -@media (max-width: 730px) { +@media (max-width: $breakpoint-tablet) { .mat-card-title, .mat-mdc-card-title { flex-direction: column; @@ -480,7 +482,7 @@ // .page-selector display:none moved to global _material-overrides.scss .full-paginator { - min-width: 500px !important; + min-width: 0 !important; } .substance-data { flex-direction: column; @@ -540,7 +542,7 @@ } } -@media (max-width: 600px) { +@media (max-width: $breakpoint-mobile) { .substance-content { flex-direction: column; @@ -549,15 +551,86 @@ } } + .full-paginator { + min-width: 0 !important; + flex-direction: column; + align-items: center; + } + + .wildcard-div { + width: 85%; + margin: 10px 0; + + mat-form-field { + flex: 1 1 auto; + } + } + + .middle-fill { + display: none; + } + + .export { + width: 100%; + flex-direction: column; + gap: 8px; + padding: 0; + + button { + width: 100%; + margin-left: 0 !important; + } + } + + .divflex { + width: 100%; + flex-direction: column; + gap: 8px; + padding: 0 30px; + + button { + width: 100%; + margin-left: 0 !important; + } + } + + mat-paginator { + width: 100%; + margin-bottom: 20px; + + ::ng-deep .mat-mdc-paginator-container { + flex-wrap: wrap; + justify-content: center; + padding: 0; + min-height: unset; + } + + ::ng-deep .mat-mdc-paginator-page-size { + margin-right: 0; + } + + ::ng-deep .mat-mdc-paginator-range-actions { + justify-content: center; + + .mat-mdc-icon-button { + width: 44px; + height: 44px; + line-height: 44px; + } + } + + ::ng-deep .mat-mdc-paginator-range-label { + margin: 0 4px; + font-size: 14px; + } + } + .controls-container { ::ng-deep { .mat-paginator-range-l.references-link { display: inline-flex; vertical-align: middle; } - abel { - margin: 0 3px 0 0; - } .mat-paginator-page-size-select { margin-left: 0; @@ -583,19 +656,6 @@ } } -@media (max-width: 505px) { - .full-paginator { - min-width: 0px !important; - } - .controls-container { - ::ng-deep { - .mat-paginator-page-size-label { - display: none; - } - } - } -} - .zoom:hover { cursor: zoom-in; } @@ -616,6 +676,11 @@ ::ng-deep .mat-mdc-form-field-infix { padding-bottom: 10px; } + + @media (max-width: $breakpoint-mobile) { + width: 94%; + margin: 0; + } } .advanced { diff --git a/src/app/core/substances-browse/substances-browse.component.ts b/src/app/core/substances-browse/substances-browse.component.ts index 5b4d3218b..a92605c46 100644 --- a/src/app/core/substances-browse/substances-browse.component.ts +++ b/src/app/core/substances-browse/substances-browse.component.ts @@ -96,6 +96,7 @@ export class SubstancesBrowseComponent implements OnInit, AfterViewInit, OnDestr // Initialized before any lifecycle hook so the first template render uses the correct state. // This prevents mat-sidenav-content from flashing at full width before the sidenav opens. readonly initialSidenavOpen = typeof window !== 'undefined' && window.innerWidth >= 1100; + isMobile = typeof window !== 'undefined' && window.innerWidth <= 600; hasBackdrop = false; view = 'cards'; private resizeTimeout: any; @@ -1201,6 +1202,7 @@ export class SubstancesBrowseComponent implements OnInit, AfterViewInit, OnDestr private processResponsiveness = () => { if (window) { + this.isMobile = window.innerWidth <= 600; if (window.innerWidth < 1100) { this.matSideNav.close(); this.isCollapsed = true; diff --git a/src/styles/_variables.scss b/src/styles/_variables.scss index 6b71b23c3..181a43f1d 100644 --- a/src/styles/_variables.scss +++ b/src/styles/_variables.scss @@ -1 +1,9 @@ -$nav-breaking-point: 990px; \ No newline at end of file +$nav-breaking-point: 990px; + +// Responsive breakpoints +$breakpoint-mobile-sm: 480px; // small phones (iPhone SE, etc.) +$breakpoint-mobile: 600px; // mobile +$breakpoint-tablet: 768px; // tablet / small laptop +$breakpoint-desktop-sm: 1100px; // compact desktop +$breakpoint-desktop-md: 1350px; // medium desktop +$breakpoint-desktop-lg: 1750px; // large desktop / wide monitor \ No newline at end of file From 39ac02aa491231c1a3d84dda1a4f3315e7da520e Mon Sep 17 00:00:00 2001 From: Iaroslav Moskviak Date: Mon, 4 May 2026 15:38:34 -0400 Subject: [PATCH 381/408] cancel button fix --- .../scheduled-job/scheduled-job.component.ts | 167 +++++++++--------- 1 file changed, 83 insertions(+), 84 deletions(-) diff --git a/src/app/core/admin/scheduled-jobs/scheduled-job/scheduled-job.component.ts b/src/app/core/admin/scheduled-jobs/scheduled-job/scheduled-job.component.ts index 20c0ac70a..6c9f32e8a 100644 --- a/src/app/core/admin/scheduled-jobs/scheduled-job/scheduled-job.component.ts +++ b/src/app/core/admin/scheduled-jobs/scheduled-job/scheduled-job.component.ts @@ -1,6 +1,6 @@ import { Component, OnInit, Input, OnDestroy } from '@angular/core'; import { AdminService } from '@gsrs-core/admin/admin.service'; -import * as moment from 'moment'; +import moment from 'moment'; import cronstrue from 'cronstrue'; import { ScheduledJob } from '@gsrs-core/admin/scheduled-jobs/scheduled-job.model'; import { take } from 'rxjs/operators'; @@ -32,7 +32,7 @@ export class ScheduledJobComponent implements OnInit, OnDestroy { ngOnInit() { this.monitor = this.pollIn; this.refresh(true); - this.occasionalApiBasePath = `${(this.configService.configData && this.configService.configData.occasionalApiBasePath)}` || ''; + this.occasionalApiBasePath = (this.configService.configData && this.configService.configData.occasionalApiBasePath) || ''; } ngOnDestroy() { @@ -50,103 +50,102 @@ export class ScheduledJobComponent implements OnInit, OnDestroy { } refresh(spawn?: boolean) { - this.adminService.fetchJob(this.currentService, this.job.id).pipe(take(1)).subscribe( response => { - this.job = response; - if (!this.job.running && this.job.lastFinished) { - const duration = moment.duration((this.job.lastFinished - this.job.lastStarted)); - let timestring = ''; - if ( duration.years() !== 0) { - timestring += duration.years() + (duration.years() > 1 ? ' years, ' : ' year, '); + this.adminService.fetchJob(this.currentService, this.job.id).pipe(take(1)).subscribe({ + next: response => { + this.job = response; + if (!this.job.running && this.job.lastFinished) { + const duration = moment.duration((this.job.lastFinished - this.job.lastStarted!)); + let timestring = ''; + if ( duration.years() !== 0) { + timestring += duration.years() + (duration.years() > 1 ? ' years, ' : ' year, '); + } + if ( duration.months() !== 0) { + timestring += duration.months() + (duration.months() > 1 ? ' months, ' : ' month, '); + } + if ( duration.days() !== 0) { + timestring += duration.days() + (duration.days() > 1 ? ' days, ' : ' day, '); + } + if ( duration.hours() !== 0) { + timestring += duration.hours() + (duration.hours() > 1 ? ' hrs, ' : ' hr, '); + } + if ( duration.minutes() !== 0) { + timestring += duration.minutes() + (duration.minutes() > 1 ? ' min, ' : ' min, '); + } + if ( duration.seconds() !== 0) { + timestring += duration.seconds() + (duration.seconds() > 1 ? ' sec' : ' sec'); + } else if (timestring === '') { + timestring = (this.job.lastFinished - this.job.lastStarted!) + ' ms'; + } + this.job.lastDurationHuman = timestring; } - if ( duration.months() !== 0) { - timestring += duration.months() + (duration.months() > 1 ? ' months, ' : ' month, '); + this.quickLoad = false; + if (this.monitor && spawn) { + this.mess = 'Polling ... ' + response.status; + if (this.job.running) { + setTimeout(() => { + this.refresh(true); + }, Math.min(this.untilNextRun(), 200)); + } else { + setTimeout(() => { + this.refresh(true); + }, Math.min(this.untilNextRun(), 10000)); + } } - if ( duration.days() !== 0) { - timestring += duration.days() + (duration.days() > 1 ? ' days, ' : ' day, '); - } - if ( duration.hours() !== 0) { - timestring += duration.hours() + (duration.hours() > 1 ? ' hrs, ' : ' hr, '); - } - if ( duration.minutes() !== 0) { - timestring += duration.minutes() + (duration.minutes() > 1 ? ' min, ' : ' min, '); - } - if ( duration.seconds() !== 0) { - timestring += duration.seconds() + (duration.seconds() > 1 ? ' sec' : ' sec'); - } else if (timestring === '') { - timestring = (this.job.lastFinished - this.job.lastStarted) + ' ms'; - } - this.job.lastDurationHuman = timestring; - - - } - this.quickLoad = false; - if (this.monitor && spawn) { - this.mess = 'Polling ... ' + response.status; - if (this.job.running) { - setTimeout(() => { - this.refresh(true); - }, Math.min(this.untilNextRun(), 200)); - } else { - setTimeout(() => { - this.refresh(true); - }, Math.min(this.untilNextRun(), 10000)); - } - } - }, error => { - this.monitor = false; - console.log(error); + }, + error: error => { + this.monitor = false; + console.log(error); + } }); } untilNextRun() { const date = new Date(); - return this.job.nextRun - ( date.getTime() - 0); + return this.job.nextRun! - date.getTime(); } stopMonitor() { this.monitor = false; -} + } -disable(serviceContext: string, job: any) { - const url =job['@disable']; - const url2 = url.replace('/api/v1/', this.occasionalApiBasePath + '/service/' + serviceContext + '/api/v1/'); - this.adminService.runJob(url2).pipe(take(1)).subscribe( response => { - this.refresh(); - }); -} + disable(serviceContext: string, job: any) { + const url = job['@disable']; + const url2 = url.replace('/api/v1/', this.occasionalApiBasePath + '/service/' + serviceContext + '/api/v1/'); + this.adminService.runJob(url2).pipe(take(1)).subscribe({ + next: () => this.refresh() + }); + } -enable(serviceContext: string, job: any) { - const url =job['@enable']; - const url2 = url.replace('/api/v1/', this.occasionalApiBasePath + '/service/' + serviceContext + '/api/v1/'); - this.adminService.runJob(url2).pipe(take(1)).subscribe( response => { - this.refresh(); - }); -} + enable(serviceContext: string, job: any) { + const url = job['@enable']; + const url2 = url.replace('/api/v1/', this.occasionalApiBasePath + '/service/' + serviceContext + '/api/v1/'); + this.adminService.runJob(url2).pipe(take(1)).subscribe({ + next: () => this.refresh() + }); + } -execute(serviceContext: string, job: any) { - this.quickLoad = true; - const url =job['@execute']; - const replace = this.occasionalApiBasePath + '/service/' + serviceContext + '/api/v1/'; - const url2 = url.replace('/api/v1/', this.occasionalApiBasePath + '/service/' + serviceContext + '/api/v1/'); - this.adminService.runJob(url2).pipe(take(1)).subscribe( response => { - this.refresh(true); - }, error => { - setTimeout(() => { - this.refresh(); - } ); - }); -} + execute(serviceContext: string, job: any) { + this.quickLoad = true; + const url = job['@execute']; + const url2 = url.replace('/api/v1/', this.occasionalApiBasePath + '/service/' + serviceContext + '/api/v1/'); + this.adminService.runJob(url2).pipe(take(1)).subscribe({ + next: () => this.refresh(true), + error: () => setTimeout(() => this.refresh()) + }); + } -cancel(serviceContext: string, job: any) { - const url =job['@cancel']; - const url2 = url.replace('/api/v1/', this.occasionalApiBasePath + '/service/' + serviceContext + '/api/v1/'); - this.adminService.runJob(url2).pipe(take(1)).subscribe( response => { - this.refresh(); - }); -} + cancel(serviceContext: string, job: any) { + const url = job['@cancel']; + if (!url) { return; } + const url2 = url.replace('/api/v1/', this.occasionalApiBasePath + '/service/' + serviceContext + '/api/v1/'); + this.adminService.runJob(url2).pipe(take(1)).subscribe({ + next: () => this.refresh(), + error: err => { console.error('Cancel failed', err); this.refresh(); } + }); + } -formatDate(ts) { - return new Date(ts) + ''; -} + formatDate(ts: number) { + return new Date(ts) + ''; + } } From efa8c9e819c785b41350c9b068ac5e65fd77a85e Mon Sep 17 00:00:00 2001 From: Iaroslav Moskviak Date: Mon, 4 May 2026 16:30:26 -0400 Subject: [PATCH 382/408] eslint upgrade and null guard --- package-lock.json | 540 ++++-------------- package.json | 10 +- .../scheduled-job/scheduled-job.component.ts | 10 +- 3 files changed, 126 insertions(+), 434 deletions(-) diff --git a/package-lock.json b/package-lock.json index 6de4045c6..81aa0d3e9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -54,11 +54,11 @@ }, "devDependencies": { "@angular-devkit/build-angular": "20.3.12", - "@angular-eslint/builder": "^19.0.2", - "@angular-eslint/eslint-plugin": "^19.0.2", - "@angular-eslint/eslint-plugin-template": "^19.0.2", - "@angular-eslint/schematics": "^19.0.2", - "@angular-eslint/template-parser": "^19.0.2", + "@angular-eslint/builder": "^20.0.0", + "@angular-eslint/eslint-plugin": "^20.0.0", + "@angular-eslint/eslint-plugin-template": "^20.0.0", + "@angular-eslint/schematics": "^20.0.0", + "@angular-eslint/template-parser": "^20.0.0", "@angular/cli": "20.3.12", "@angular/compiler-cli": "20.3.14", "@angular/language-service": "20.3.14", @@ -702,40 +702,40 @@ } }, "node_modules/@angular-devkit/schematics": { - "version": "19.2.19", - "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-19.2.19.tgz", - "integrity": "sha512-J4Jarr0SohdrHcb40gTL4wGPCQ952IMWF1G/MSAQfBAPvA9ZKApYhpxcY7PmehVePve+ujpus1dGsJ7dPxz8Kg==", + "version": "20.3.25", + "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-20.3.25.tgz", + "integrity": "sha512-IB0IHf8ZRqr69hT/XIfHkYLPAYHWQ/WUc6+fKHBwq58jJlm/y5QUeTEuXvJ18IX/+hUIqw+E2Q3T0N9rLDLzXg==", "dev": true, "license": "MIT", "dependencies": { - "@angular-devkit/core": "19.2.19", + "@angular-devkit/core": "20.3.25", "jsonc-parser": "3.3.1", "magic-string": "0.30.17", - "ora": "5.4.1", - "rxjs": "7.8.1" + "ora": "8.2.0", + "rxjs": "7.8.2" }, "engines": { - "node": "^18.19.1 || ^20.11.1 || >=22.0.0", + "node": "^20.19.0 || ^22.12.0 || >=24.0.0", "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", "yarn": ">= 1.13.0" } }, "node_modules/@angular-devkit/schematics/node_modules/@angular-devkit/core": { - "version": "19.2.19", - "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-19.2.19.tgz", - "integrity": "sha512-JbLL+4IMLMBgjLZlnPG4lYDfz4zGrJ/s6Aoon321NJKuw1Kb1k5KpFu9dUY0BqLIe8xPQ2UJBpI+xXdK5MXMHQ==", + "version": "20.3.25", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-20.3.25.tgz", + "integrity": "sha512-pSfeWEoS1y9zxqTOw0Etj2NXkRmp/aU52wdnyq3KqVA9cJtgxtlOHlBBNswTil4dReojEy+0K1xJm+mLuWuLxA==", "dev": true, "license": "MIT", "dependencies": { - "ajv": "8.17.1", + "ajv": "8.18.0", "ajv-formats": "3.0.1", "jsonc-parser": "3.3.1", - "picomatch": "4.0.2", - "rxjs": "7.8.1", - "source-map": "0.7.4" + "picomatch": "4.0.4", + "rxjs": "7.8.2", + "source-map": "0.7.6" }, "engines": { - "node": "^18.19.1 || ^20.11.1 || >=22.0.0", + "node": "^20.19.0 || ^22.12.0 || >=24.0.0", "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", "yarn": ">= 1.13.0" }, @@ -748,103 +748,27 @@ } } }, - "node_modules/@angular-devkit/schematics/node_modules/cli-cursor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", - "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", - "dev": true, - "license": "MIT", - "dependencies": { - "restore-cursor": "^3.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@angular-devkit/schematics/node_modules/is-interactive": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz", - "integrity": "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/@angular-devkit/schematics/node_modules/is-unicode-supported": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", - "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@angular-devkit/schematics/node_modules/log-symbols": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", - "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", + "node_modules/@angular-devkit/schematics/node_modules/ajv": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", + "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", "dev": true, "license": "MIT", "dependencies": { - "chalk": "^4.1.0", - "is-unicode-supported": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@angular-devkit/schematics/node_modules/onetime": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", - "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", - "dev": true, - "license": "MIT", - "dependencies": { - "mimic-fn": "^2.1.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@angular-devkit/schematics/node_modules/ora": { - "version": "5.4.1", - "resolved": "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz", - "integrity": "sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "bl": "^4.1.0", - "chalk": "^4.1.0", - "cli-cursor": "^3.1.0", - "cli-spinners": "^2.5.0", - "is-interactive": "^1.0.0", - "is-unicode-supported": "^0.1.0", - "log-symbols": "^4.1.0", - "strip-ansi": "^6.0.0", - "wcwidth": "^1.0.1" - }, - "engines": { - "node": ">=10" + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" } }, "node_modules/@angular-devkit/schematics/node_modules/picomatch": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", - "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "dev": true, "license": "MIT", "engines": { @@ -854,135 +778,48 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/@angular-devkit/schematics/node_modules/restore-cursor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", - "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", + "node_modules/@angular-devkit/schematics/node_modules/rxjs": { + "version": "7.8.2", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz", + "integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==", "dev": true, - "license": "MIT", + "license": "Apache-2.0", "dependencies": { - "onetime": "^5.1.0", - "signal-exit": "^3.0.2" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@angular-devkit/schematics/node_modules/signal-exit": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/@angular-devkit/schematics/node_modules/source-map": { - "version": "0.7.4", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", - "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">= 8" + "tslib": "^2.1.0" } }, "node_modules/@angular-eslint/builder": { - "version": "19.8.1", - "resolved": "https://registry.npmjs.org/@angular-eslint/builder/-/builder-19.8.1.tgz", - "integrity": "sha512-NOMkw0xgDoDVCLkL5nkkvdd3ouDYkOGqtEmabTR7N4/kQnk1R4coOTWGCqAgMXCFdxlyjuxquDwuJ+yni81pRg==", + "version": "20.7.0", + "resolved": "https://registry.npmjs.org/@angular-eslint/builder/-/builder-20.7.0.tgz", + "integrity": "sha512-qgf4Cfs1z0VsVpzF/OnxDRvBp60OIzeCsp4mzlckWYVniKo19EPIN6kFDol5eTAIOMPgiBQlMIwgQMHgocXEig==", "dev": true, "license": "MIT", "dependencies": { - "@angular-devkit/architect": ">= 0.1900.0 < 0.2000.0", - "@angular-devkit/core": ">= 19.0.0 < 20.0.0" + "@angular-devkit/architect": ">= 0.2000.0 < 0.2100.0", + "@angular-devkit/core": ">= 20.0.0 < 21.0.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": "*" } }, - "node_modules/@angular-eslint/builder/node_modules/@angular-devkit/architect": { - "version": "0.1902.19", - "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1902.19.tgz", - "integrity": "sha512-iexYDIYpGAeAU7T60bGcfrGwtq1bxpZixYxWuHYiaD1b5baQgNSfd1isGEOh37GgDNsf4In9i2LOLPm0wBdtgQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@angular-devkit/core": "19.2.19", - "rxjs": "7.8.1" - }, - "engines": { - "node": "^18.19.1 || ^20.11.1 || >=22.0.0", - "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", - "yarn": ">= 1.13.0" - } - }, - "node_modules/@angular-eslint/builder/node_modules/@angular-devkit/core": { - "version": "19.2.19", - "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-19.2.19.tgz", - "integrity": "sha512-JbLL+4IMLMBgjLZlnPG4lYDfz4zGrJ/s6Aoon321NJKuw1Kb1k5KpFu9dUY0BqLIe8xPQ2UJBpI+xXdK5MXMHQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ajv": "8.17.1", - "ajv-formats": "3.0.1", - "jsonc-parser": "3.3.1", - "picomatch": "4.0.2", - "rxjs": "7.8.1", - "source-map": "0.7.4" - }, - "engines": { - "node": "^18.19.1 || ^20.11.1 || >=22.0.0", - "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", - "yarn": ">= 1.13.0" - }, - "peerDependencies": { - "chokidar": "^4.0.0" - }, - "peerDependenciesMeta": { - "chokidar": { - "optional": true - } - } - }, - "node_modules/@angular-eslint/builder/node_modules/picomatch": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", - "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/@angular-eslint/builder/node_modules/source-map": { - "version": "0.7.4", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", - "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">= 8" - } - }, "node_modules/@angular-eslint/bundled-angular-compiler": { - "version": "19.8.1", - "resolved": "https://registry.npmjs.org/@angular-eslint/bundled-angular-compiler/-/bundled-angular-compiler-19.8.1.tgz", - "integrity": "sha512-WXi1YbSs7SIQo48u+fCcc5Nt14/T4QzYQPLZUnjtsUXPgQG7ZoahhcGf7PPQ+n0V3pSopHOlSHwqK+tSsYK87A==", + "version": "20.7.0", + "resolved": "https://registry.npmjs.org/@angular-eslint/bundled-angular-compiler/-/bundled-angular-compiler-20.7.0.tgz", + "integrity": "sha512-9KPz24YoiL0SvTtTX6sd1zmysU5cKOCcmpEiXkCoO3L2oYZGlVxmMT4hfSaHMt8qmfvV2KzQMoR6DZM84BwRzQ==", "dev": true, "license": "MIT" }, "node_modules/@angular-eslint/eslint-plugin": { - "version": "19.8.1", - "resolved": "https://registry.npmjs.org/@angular-eslint/eslint-plugin/-/eslint-plugin-19.8.1.tgz", - "integrity": "sha512-wZEBMPwD2TRhifG751hcj137EMIEaFmsxRB2EI+vfINCgPnFGSGGOHXqi8aInn9fXqHs7VbXkAzXYdBsvy1m4Q==", + "version": "20.7.0", + "resolved": "https://registry.npmjs.org/@angular-eslint/eslint-plugin/-/eslint-plugin-20.7.0.tgz", + "integrity": "sha512-aHH2YTiaonojsKN+y2z4IMugCwdsH/dYIjYBig6kfoSPyf9rGK4zx+gnNGq/pGRjF3bOYrmFgIviYpQVb80inQ==", "dev": true, "license": "MIT", "dependencies": { - "@angular-eslint/bundled-angular-compiler": "19.8.1", - "@angular-eslint/utils": "19.8.1" + "@angular-eslint/bundled-angular-compiler": "20.7.0", + "@angular-eslint/utils": "20.7.0", + "ts-api-utils": "^2.1.0" }, "peerDependencies": { "@typescript-eslint/utils": "^7.11.0 || ^8.0.0", @@ -991,101 +828,76 @@ } }, "node_modules/@angular-eslint/eslint-plugin-template": { - "version": "19.8.1", - "resolved": "https://registry.npmjs.org/@angular-eslint/eslint-plugin-template/-/eslint-plugin-template-19.8.1.tgz", - "integrity": "sha512-0ZVQldndLrDfB0tzFe/uIwvkUcakw8qGxvkEU0l7kSbv/ngNQ/qrkRi7P64otB15inIDUNZI2jtmVat52dqSfQ==", + "version": "20.7.0", + "resolved": "https://registry.npmjs.org/@angular-eslint/eslint-plugin-template/-/eslint-plugin-template-20.7.0.tgz", + "integrity": "sha512-WFmvW2vBR6ExsSKEaActQTteyw6ikWyuJau9XmWEPFd+2eusEt/+wO21ybjDn3uc5FTp1IcdhfYy+U5OdDjH5w==", "dev": true, "license": "MIT", "dependencies": { - "@angular-eslint/bundled-angular-compiler": "19.8.1", - "@angular-eslint/utils": "19.8.1", + "@angular-eslint/bundled-angular-compiler": "20.7.0", + "@angular-eslint/utils": "20.7.0", "aria-query": "5.3.2", "axobject-query": "4.1.0" }, "peerDependencies": { - "@angular-eslint/template-parser": "19.8.1", + "@angular-eslint/template-parser": "20.7.0", "@typescript-eslint/types": "^7.11.0 || ^8.0.0", "@typescript-eslint/utils": "^7.11.0 || ^8.0.0", "eslint": "^8.57.0 || ^9.0.0", "typescript": "*" } }, - "node_modules/@angular-eslint/schematics": { - "version": "19.8.1", - "resolved": "https://registry.npmjs.org/@angular-eslint/schematics/-/schematics-19.8.1.tgz", - "integrity": "sha512-MKzfO3puOCuQFgP8XDUkEr5eaqcCQLAdYLLMcywEO/iRs1eRHL46+rkW+SjDp1cUqlxKtu+rLiTYr0T/O4fi9Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@angular-devkit/core": ">= 19.0.0 < 20.0.0", - "@angular-devkit/schematics": ">= 19.0.0 < 20.0.0", - "@angular-eslint/eslint-plugin": "19.8.1", - "@angular-eslint/eslint-plugin-template": "19.8.1", - "ignore": "7.0.5", - "semver": "7.7.2", - "strip-json-comments": "3.1.1" - } - }, - "node_modules/@angular-eslint/schematics/node_modules/@angular-devkit/core": { - "version": "19.2.19", - "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-19.2.19.tgz", - "integrity": "sha512-JbLL+4IMLMBgjLZlnPG4lYDfz4zGrJ/s6Aoon321NJKuw1Kb1k5KpFu9dUY0BqLIe8xPQ2UJBpI+xXdK5MXMHQ==", + "node_modules/@angular-eslint/eslint-plugin/node_modules/ts-api-utils": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.5.0.tgz", + "integrity": "sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==", "dev": true, "license": "MIT", - "dependencies": { - "ajv": "8.17.1", - "ajv-formats": "3.0.1", - "jsonc-parser": "3.3.1", - "picomatch": "4.0.2", - "rxjs": "7.8.1", - "source-map": "0.7.4" - }, "engines": { - "node": "^18.19.1 || ^20.11.1 || >=22.0.0", - "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", - "yarn": ">= 1.13.0" + "node": ">=18.12" }, "peerDependencies": { - "chokidar": "^4.0.0" - }, - "peerDependenciesMeta": { - "chokidar": { - "optional": true - } + "typescript": ">=4.8.4" } }, - "node_modules/@angular-eslint/schematics/node_modules/picomatch": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", - "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", + "node_modules/@angular-eslint/schematics": { + "version": "20.7.0", + "resolved": "https://registry.npmjs.org/@angular-eslint/schematics/-/schematics-20.7.0.tgz", + "integrity": "sha512-S0onfRipDUIL6gFGTFjiWwUDhi42XYrBoi3kJ3wBbKBeIgYv9SP1ppTKDD4ZoDaDU9cQE8nToX7iPn9ifMw6eQ==", "dev": true, "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" + "dependencies": { + "@angular-devkit/core": ">= 20.0.0 < 21.0.0", + "@angular-devkit/schematics": ">= 20.0.0 < 21.0.0", + "@angular-eslint/eslint-plugin": "20.7.0", + "@angular-eslint/eslint-plugin-template": "20.7.0", + "ignore": "7.0.5", + "semver": "7.7.3", + "strip-json-comments": "3.1.1" } }, - "node_modules/@angular-eslint/schematics/node_modules/source-map": { - "version": "0.7.4", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", - "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==", + "node_modules/@angular-eslint/schematics/node_modules/semver": { + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", "dev": true, - "license": "BSD-3-Clause", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, "engines": { - "node": ">= 8" + "node": ">=10" } }, "node_modules/@angular-eslint/template-parser": { - "version": "19.8.1", - "resolved": "https://registry.npmjs.org/@angular-eslint/template-parser/-/template-parser-19.8.1.tgz", - "integrity": "sha512-pQiOg+se1AU/ncMlnJ9V6xYnMQ84qI1BGWuJpbU6A99VTXJg90scg0+T7DWmKssR1YjP5qmmBtrZfKsHEcLW/A==", + "version": "20.7.0", + "resolved": "https://registry.npmjs.org/@angular-eslint/template-parser/-/template-parser-20.7.0.tgz", + "integrity": "sha512-CVskZnF38IIxVVlKWi1VCz7YH/gHMJu2IY9bD1AVoBBGIe0xA4FRXJkW2Y+EDs9vQqZTkZZljhK5gL65Ro1PeQ==", "dev": true, "license": "MIT", "dependencies": { - "@angular-eslint/bundled-angular-compiler": "19.8.1", - "eslint-scope": "^8.0.2" + "@angular-eslint/bundled-angular-compiler": "20.7.0", + "eslint-scope": "^9.0.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", @@ -1093,13 +905,13 @@ } }, "node_modules/@angular-eslint/utils": { - "version": "19.8.1", - "resolved": "https://registry.npmjs.org/@angular-eslint/utils/-/utils-19.8.1.tgz", - "integrity": "sha512-gVDKYWmAjeTPtaYmddT/HS03fCebXJtrk8G1MouQIviZbHqLjap6TbVlzlkBigRzaF0WnFnrDduQslkJzEdceA==", + "version": "20.7.0", + "resolved": "https://registry.npmjs.org/@angular-eslint/utils/-/utils-20.7.0.tgz", + "integrity": "sha512-B6EJHbsk2W/lnS3kS/gm56VGvX735419z/DzgbRDcOvqMGMLwD1ILzv5OTEcL1rzpnB0AHW+IxOu6y/aCzSNUA==", "dev": true, "license": "MIT", "dependencies": { - "@angular-eslint/bundled-angular-compiler": "19.8.1" + "@angular-eslint/bundled-angular-compiler": "20.7.0" }, "peerDependencies": { "@typescript-eslint/utils": "^7.11.0 || ^8.0.0", @@ -6838,6 +6650,13 @@ "@types/estree": "*" } }, + "node_modules/@types/esrecurse": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@types/esrecurse/-/esrecurse-4.3.1.tgz", + "integrity": "sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/estree": { "version": "0.0.51", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.51.tgz", @@ -8337,27 +8156,6 @@ "node": ">= 0.6.0" } }, - "node_modules/base64-js": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", - "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, "node_modules/base64id": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz", @@ -8437,33 +8235,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/bl": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", - "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", - "dev": true, - "license": "MIT", - "dependencies": { - "buffer": "^5.5.0", - "inherits": "^2.0.4", - "readable-stream": "^3.4.0" - } - }, - "node_modules/bl/node_modules/readable-stream": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", - "dev": true, - "license": "MIT", - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } - }, "node_modules/blocking-proxy": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/blocking-proxy/-/blocking-proxy-1.0.1.tgz", @@ -8655,31 +8426,6 @@ "node": ">= 0.4.0" } }, - "node_modules/buffer": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", - "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT", - "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.1.13" - } - }, "node_modules/buffer-crc32": { "version": "0.2.13", "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", @@ -9324,16 +9070,6 @@ "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, - "node_modules/clone": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", - "integrity": "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.8" - } - }, "node_modules/clone-deep": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz", @@ -10216,19 +9952,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/defaults": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz", - "integrity": "sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==", - "dev": true, - "license": "MIT", - "dependencies": { - "clone": "^1.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/defiant.js": { "version": "2.2.6", "resolved": "https://registry.npmjs.org/defiant.js/-/defiant.js-2.2.6.tgz", @@ -11427,22 +11150,31 @@ } }, "node_modules/eslint-scope": { - "version": "8.4.0", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", - "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-9.1.2.tgz", + "integrity": "sha512-xS90H51cKw0jltxmvmHy2Iai1LIqrfbw57b79w/J7MfvDfkIkFZ+kj6zC3BjtUwh150HsSSdxXZcsuv72miDFQ==", "dev": true, "license": "BSD-2-Clause", "dependencies": { + "@types/esrecurse": "^4.3.1", + "@types/estree": "^1.0.8", "esrecurse": "^4.3.0", "estraverse": "^5.2.0" }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": "^20.19.0 || ^22.13.0 || >=24" }, "funding": { "url": "https://opencollective.com/eslint" } }, + "node_modules/eslint-scope/node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, "node_modules/eslint-visitor-keys": { "version": "3.4.3", "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", @@ -13387,27 +13119,6 @@ "postcss": "^8.1.0" } }, - "node_modules/ieee754": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", - "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "BSD-3-Clause" - }, "node_modules/ienoopen": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/ienoopen/-/ienoopen-1.1.0.tgz", @@ -16146,16 +15857,6 @@ "node": ">= 0.6" } }, - "node_modules/mimic-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, "node_modules/mimic-function": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/mimic-function/-/mimic-function-5.0.1.tgz", @@ -22088,6 +21789,7 @@ "version": "5.9.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, "license": "Apache-2.0", "bin": { "tsc": "bin/tsc", @@ -22646,16 +22348,6 @@ "minimalistic-assert": "^1.0.0" } }, - "node_modules/wcwidth": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", - "integrity": "sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==", - "dev": true, - "license": "MIT", - "dependencies": { - "defaults": "^1.0.3" - } - }, "node_modules/weak-lru-cache": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/weak-lru-cache/-/weak-lru-cache-1.2.2.tgz", diff --git a/package.json b/package.json index 3eef47d14..739a3b9af 100644 --- a/package.json +++ b/package.json @@ -79,11 +79,11 @@ }, "devDependencies": { "@angular-devkit/build-angular": "20.3.12", - "@angular-eslint/builder": "^19.0.2", - "@angular-eslint/eslint-plugin": "^19.0.2", - "@angular-eslint/eslint-plugin-template": "^19.0.2", - "@angular-eslint/schematics": "^19.0.2", - "@angular-eslint/template-parser": "^19.0.2", + "@angular-eslint/builder": "^20.0.0", + "@angular-eslint/eslint-plugin": "^20.0.0", + "@angular-eslint/eslint-plugin-template": "^20.0.0", + "@angular-eslint/schematics": "^20.0.0", + "@angular-eslint/template-parser": "^20.0.0", "@angular/cli": "20.3.12", "@angular/compiler-cli": "20.3.14", "@angular/language-service": "20.3.14", diff --git a/src/app/core/admin/scheduled-jobs/scheduled-job/scheduled-job.component.ts b/src/app/core/admin/scheduled-jobs/scheduled-job/scheduled-job.component.ts index 6c9f32e8a..910114ac9 100644 --- a/src/app/core/admin/scheduled-jobs/scheduled-job/scheduled-job.component.ts +++ b/src/app/core/admin/scheduled-jobs/scheduled-job/scheduled-job.component.ts @@ -53,8 +53,8 @@ export class ScheduledJobComponent implements OnInit, OnDestroy { this.adminService.fetchJob(this.currentService, this.job.id).pipe(take(1)).subscribe({ next: response => { this.job = response; - if (!this.job.running && this.job.lastFinished) { - const duration = moment.duration((this.job.lastFinished - this.job.lastStarted!)); + if (!this.job.running && this.job.lastFinished && this.job.lastStarted) { + const duration = moment.duration((this.job.lastFinished - this.job.lastStarted)); let timestring = ''; if ( duration.years() !== 0) { timestring += duration.years() + (duration.years() > 1 ? ' years, ' : ' year, '); @@ -74,7 +74,7 @@ export class ScheduledJobComponent implements OnInit, OnDestroy { if ( duration.seconds() !== 0) { timestring += duration.seconds() + (duration.seconds() > 1 ? ' sec' : ' sec'); } else if (timestring === '') { - timestring = (this.job.lastFinished - this.job.lastStarted!) + ' ms'; + timestring = (this.job.lastFinished - this.job.lastStarted) + ' ms'; } this.job.lastDurationHuman = timestring; } @@ -100,8 +100,8 @@ export class ScheduledJobComponent implements OnInit, OnDestroy { } untilNextRun() { - const date = new Date(); - return this.job.nextRun! - date.getTime(); + if (!this.job.nextRun) { return Number.MAX_SAFE_INTEGER; } + return this.job.nextRun - new Date().getTime(); } stopMonitor() { From 1614ae48f4e079b2f070d918bc6539331ee03589 Mon Sep 17 00:00:00 2001 From: Iaroslav Moskviak Date: Mon, 4 May 2026 17:02:23 -0400 Subject: [PATCH 383/408] deleted unused Buffer --- src/app/core/substance-form/substance-form.component.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/app/core/substance-form/substance-form.component.ts b/src/app/core/substance-form/substance-form.component.ts index 0b4857342..d4a54a0d9 100644 --- a/src/app/core/substance-form/substance-form.component.ts +++ b/src/app/core/substance-form/substance-form.component.ts @@ -47,7 +47,6 @@ import {FragmentWizardComponent} from '@gsrs-core/admin/fragment-wizard/fragment import {SubstanceDraftsComponent} from '@gsrs-core/substance-form/substance-drafts/substance-drafts.component'; import {UtilsService} from '@gsrs-core/utils'; import {ungzip, deflate, inflate} from 'pako'; -import {Buffer} from 'buffer'; import {AdminService} from '@gsrs-core/admin/admin.service'; import {MatButtonToggleChange} from "@angular/material/button-toggle"; import {tr} from "cronstrue/dist/i18n/locales/tr"; @@ -514,8 +513,7 @@ export class SubstanceFormComponent implements OnInit, AfterViewInit, AfterViewC gunzip(t): string { - const gezipedData = Buffer.from(t, 'base64') - const gzipedDataArray = Uint8Array.from(gezipedData); + const gzipedDataArray = Uint8Array.from(atob(t), c => c.charCodeAt(0)); const ungzipedData = ungzip(gzipedDataArray); return new TextDecoder().decode(ungzipedData); } From 72ca8d5f2216448876d7fbb7581110e5b7d81935 Mon Sep 17 00:00:00 2001 From: Jaroslav Iha Date: Tue, 5 May 2026 11:13:02 +0200 Subject: [PATCH 384/408] fix deleting export when finished --- .../download-monitor/download-monitor.component.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/core/auth/user-downloads/download-monitor/download-monitor.component.html b/src/app/core/auth/user-downloads/download-monitor/download-monitor.component.html index 9b79e0faa..5e78eef0f 100644 --- a/src/app/core/auth/user-downloads/download-monitor/download-monitor.component.html +++ b/src/app/core/auth/user-downloads/download-monitor/download-monitor.component.html @@ -126,7 +126,7 @@
    - From 257f41d996783756ec68da795fe00fa9e83d35d1 Mon Sep 17 00:00:00 2001 From: Iaroslav Moskviak Date: Tue, 5 May 2026 11:47:10 -0400 Subject: [PATCH 385/408] fix: patch Angular/deps security vulnerabilities, remove Protractor --- .eslintrc.json | 12 +- angular.json | 24 - e2e/.eslintrc.json | 45 - e2e/protractor.conf.js | 28 - e2e/src/app.e2e-spec.ts | 14 - e2e/src/app.po.ts | 11 - e2e/tsconfig.e2e.json | 13 - package-lock.json | 8693 ++++++++--------- package.json | 40 +- .../user-management.component.ts | 1 - src/app/core/bulkQuery/bulk-query.service.ts | 1 - .../structure-search.component.ts | 1 - .../substance-names.component.ts | 1 - ...stance-form-constituents-card.component.ts | 1 - src/app/core/substance/substance.service.ts | 1 - .../application-darrts-details.component.ts | 1 - .../cross-entity-search.component.ts | 1 - .../cross-entity-search.service.ts | 1 - .../impurities-test-form.component.scss | 2 +- ...rmacology-assay-data-import.component.scss | 2 +- ...tro-pharmacology-assay-form.component.scss | 2 +- ...-pharmacology-assayset-form.component.scss | 2 +- .../invitro-pharmacology-form.component.scss | 2 +- ...o-pharmacology-summary-form.component.scss | 2 +- ...ology-screening-data-import.component.scss | 2 +- .../invitro-pharmacology.component.scss | 2 +- 26 files changed, 4007 insertions(+), 4898 deletions(-) delete mode 100644 e2e/.eslintrc.json delete mode 100644 e2e/protractor.conf.js delete mode 100644 e2e/src/app.e2e-spec.ts delete mode 100644 e2e/src/app.po.ts delete mode 100644 e2e/tsconfig.e2e.json diff --git a/.eslintrc.json b/.eslintrc.json index 0e8a9c47e..c8374d99b 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -10,17 +10,17 @@ ], "parserOptions": { "project": [ - "tsconfig.json", - "e2e/tsconfig.json" - ], - "createDefaultProgram": true + "tsconfig.json" + ] }, + "plugins": ["@typescript-eslint"], "extends": [ - "plugin:@angular-eslint/ng-cli-compat", - "plugin:@angular-eslint/ng-cli-compat--formatting-add-on", + "plugin:@angular-eslint/recommended", "plugin:@angular-eslint/template/process-inline-templates" ], "rules": { + "@angular-eslint/prefer-standalone": "off", + "@angular-eslint/prefer-inject": "off", "@typescript-eslint/consistent-type-definitions": "error", "@typescript-eslint/dot-notation": "off", "@typescript-eslint/explicit-member-accessibility": [ diff --git a/angular.json b/angular.json index 0c23fc3c4..213df23ee 100644 --- a/angular.json +++ b/angular.json @@ -309,30 +309,6 @@ } } }, - "gsrs-client-e2e": { - "root": "e2e/", - "projectType": "application", - "architect": { - "e2e": { - "builder": "@angular-devkit/build-angular:protractor", - "options": { - "protractorConfig": "e2e/protractor.conf.js", - "devServerTarget": "gsrs-client:serve" - }, - "configurations": { - "production": { - "devServerTarget": "gsrs-client:serve:production" - } - } - }, - "lint": { - "builder": "@angular-eslint/builder:lint", - "options": { - "lintFilePatterns": ["e2e//**/*.ts", "e2e//**/*.html"] - } - } - } - }, "jsdraw-wrapper": { "root": "projects/jsdraw-wrapper", "sourceRoot": "projects/jsdraw-wrapper/src", diff --git a/e2e/.eslintrc.json b/e2e/.eslintrc.json deleted file mode 100644 index 9c9ef7f55..000000000 --- a/e2e/.eslintrc.json +++ /dev/null @@ -1,45 +0,0 @@ -{ - "extends": "../.eslintrc.json", - "ignorePatterns": [ - "!**/*" - ], - "overrides": [ - { - "files": [ - "*.ts" - ], - "parserOptions": { - "project": [ - "e2e//.eslintrc.json" - //"e2e//tsconfig.app.json", - //"e2e//tsconfig.spec.json", - //"e2e//e2e/tsconfig.json" - ], - "createDefaultProgram": true - }, - "rules": { - "@typescript-eslint/consistent-type-definitions": "error", - "@typescript-eslint/dot-notation": "off", - "@typescript-eslint/explicit-member-accessibility": [ - "off", - { - "accessibility": "explicit" - } - ], - "brace-style": [ - "error", - "1tbs" - ], - "id-blacklist": "off", - "id-match": "off", - "no-underscore-dangle": "off" - } - }, - { - "files": [ - "*.html" - ], - "rules": {} - } - ] -} diff --git a/e2e/protractor.conf.js b/e2e/protractor.conf.js deleted file mode 100644 index ba328a64b..000000000 --- a/e2e/protractor.conf.js +++ /dev/null @@ -1,28 +0,0 @@ -// Protractor configuration file, see link for more information -// https://github.com/angular/protractor/blob/master/lib/config.ts - -const { SpecReporter } = require('jasmine-spec-reporter'); - -exports.config = { - allScriptsTimeout: 11000, - specs: [ - './src/**/*.e2e-spec.ts' - ], - capabilities: { - 'browserName': 'chrome' - }, - directConnect: true, - baseUrl: 'http://localhost:4200/', - framework: 'jasmine', - jasmineNodeOpts: { - showColors: true, - defaultTimeoutInterval: 30000, - print: function() {} - }, - onPrepare() { - require('ts-node').register({ - project: require('path').join(__dirname, './tsconfig.e2e.json') - }); - jasmine.getEnv().addReporter(new SpecReporter({ spec: { displayStacktrace: true } })); - } -}; \ No newline at end of file diff --git a/e2e/src/app.e2e-spec.ts b/e2e/src/app.e2e-spec.ts deleted file mode 100644 index 1820b96b2..000000000 --- a/e2e/src/app.e2e-spec.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { AppPage } from './app.po'; - -describe('workspace-project App', () => { - let page: AppPage; - - beforeEach(() => { - page = new AppPage(); - }); - - it('should display welcome message', () => { - page.navigateTo(); - expect(page.getParagraphText()).toEqual('Welcome to gsrs-client!'); - }); -}); diff --git a/e2e/src/app.po.ts b/e2e/src/app.po.ts deleted file mode 100644 index 625420f7c..000000000 --- a/e2e/src/app.po.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { browser, by, element } from 'protractor'; - -export class AppPage { - navigateTo() { - return browser.get('/'); - } - - getParagraphText() { - return element(by.css('app-root h1')).getText(); - } -} diff --git a/e2e/tsconfig.e2e.json b/e2e/tsconfig.e2e.json deleted file mode 100644 index 43d5b3064..000000000 --- a/e2e/tsconfig.e2e.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "extends": "../tsconfig.json", - "compilerOptions": { - "outDir": "../out-tsc/app", - "module": "commonjs", - "target": "es5", - "types": [ - "jasmine", - "jasminewd2", - "node" - ] - } -} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 81aa0d3e9..b1de93ffc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,20 +8,20 @@ "name": "gsrs-client", "version": "0.0.0", "dependencies": { - "@angular/animations": "20.3.14", + "@angular/animations": "^20.3.19", "@angular/cdk": "^20.2.14", - "@angular/common": "20.3.14", - "@angular/compiler": "20.3.14", - "@angular/core": "20.3.14", - "@angular/forms": "20.3.14", + "@angular/common": "^20.3.19", + "@angular/compiler": "^20.3.19", + "@angular/core": "^20.3.19", + "@angular/forms": "^20.3.19", "@angular/material": "^20.2.14", - "@angular/platform-browser": "20.3.14", - "@angular/platform-browser-dynamic": "20.3.14", - "@angular/platform-server": "20.3.14", - "@angular/router": "20.3.14", - "@angular/ssr": "^20.3.12", + "@angular/platform-browser": "^20.3.19", + "@angular/platform-browser-dynamic": "^20.3.19", + "@angular/platform-server": "^20.3.19", + "@angular/router": "^20.3.19", + "@angular/ssr": "^20.3.25", "@types/hammerjs": "2.0.36", - "compression": "1.7.3", + "compression": "^1.8.1", "cronstrue": "1.94.0", "deep-equal": "^2.2.3", "defiant.js": "2.2.6", @@ -34,13 +34,14 @@ "jsonpath": "^1.1.1", "jspdf": "^2.5.1", "jspdf-autotable": "^3.8.2", - "lodash": "4.17.21", + "lodash": "^4.18.1", "lucene-query-parser": "1.2.0", "moment": "2.29.4", "ng-multiselect-dropdown": "^1.0.0", "ngx-json-viewer": "^3.2.1", "ngx-moment": "^6.0.2", "ngx-schema-form": "^2.14.1", + "pako": "^2.1.0", "primeng": "^20.3.0", "reflect-metadata": "0.1.13", "rxjs": "7.8.1", @@ -53,18 +54,17 @@ "zone.js": "~0.15.1" }, "devDependencies": { - "@angular-devkit/build-angular": "20.3.12", + "@angular-devkit/build-angular": "^20.3.25", "@angular-eslint/builder": "^20.0.0", "@angular-eslint/eslint-plugin": "^20.0.0", "@angular-eslint/eslint-plugin-template": "^20.0.0", "@angular-eslint/schematics": "^20.0.0", "@angular-eslint/template-parser": "^20.0.0", - "@angular/cli": "20.3.12", - "@angular/compiler-cli": "20.3.14", - "@angular/language-service": "20.3.14", + "@angular/cli": "^20.3.25", + "@angular/compiler-cli": "^20.3.19", + "@angular/language-service": "^20.3.19", "@types/estree": "0.0.51", "@types/jasmine": "3.10.3", - "@types/jasminewd2": "2.0.10", "@types/lodash": "4.14.178", "@types/node": "^22.19.1", "@types/sprintf-js": "^1.1.4", @@ -80,14 +80,13 @@ "husky": "7.0.4", "jasmine-core": "4.0.0", "jasmine-spec-reporter": "7.0.0", - "karma": "6.3.15", + "karma": "^6.4.4", "karma-chrome-launcher": "3.1.0", "karma-coverage-istanbul-reporter": "3.0.3", "karma-jasmine": "4.0.1", "karma-jasmine-html-reporter": "1.7.0", "mkdirp": "1.0.4", "ng-packagr": "20.3.2", - "protractor": "7.0.0", "raw-loader": "4.0.2", "rimraf": "3.0.2", "sass": "1.80.6", @@ -322,13 +321,13 @@ } }, "node_modules/@angular-devkit/architect": { - "version": "0.2003.12", - "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.2003.12.tgz", - "integrity": "sha512-5H40lAFF4CKY32C4HOp6bTlOF1f4WsGCwe7FjFQp9A+T7yoCBiHpIWt2JKTwV4sBoTKVDZOnuf0GG+UVKjQT4A==", + "version": "0.2003.25", + "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.2003.25.tgz", + "integrity": "sha512-39pTqt4wSmpD1WeCee46oSGXRh6TR1PFd9GZEwyZoMvBTMs8mE2sXGxUgd4Qyi5CkQ1XnCM5XfgJcjUWUQRoGg==", "dev": true, "license": "MIT", "dependencies": { - "@angular-devkit/core": "20.3.12", + "@angular-devkit/core": "20.3.25", "rxjs": "7.8.2" }, "engines": { @@ -348,17 +347,17 @@ } }, "node_modules/@angular-devkit/build-angular": { - "version": "20.3.12", - "resolved": "https://registry.npmjs.org/@angular-devkit/build-angular/-/build-angular-20.3.12.tgz", - "integrity": "sha512-HPepPbJA5vprYTWJaSCfpk0s1bPT6Ui6VjFOSb9oY+p9iq+MGkuB1I+swNcRcMLttyMD+FpbMd27F8jSeX5XVw==", + "version": "20.3.25", + "resolved": "https://registry.npmjs.org/@angular-devkit/build-angular/-/build-angular-20.3.25.tgz", + "integrity": "sha512-jp2sbJhbVRT65RbGENY/lz3Z0W0D50Af3xzENmLDLBVp9uhlpIPSTC9LG4CEPV7T6H5Oq6ymHKM+OO5WwTlHHA==", "dev": true, "license": "MIT", "dependencies": { "@ampproject/remapping": "2.3.0", - "@angular-devkit/architect": "0.2003.12", - "@angular-devkit/build-webpack": "0.2003.12", - "@angular-devkit/core": "20.3.12", - "@angular/build": "20.3.12", + "@angular-devkit/architect": "0.2003.25", + "@angular-devkit/build-webpack": "0.2003.25", + "@angular-devkit/core": "20.3.25", + "@angular/build": "20.3.25", "@babel/core": "7.28.3", "@babel/generator": "7.28.3", "@babel/helper-annotate-as-pure": "7.27.3", @@ -369,14 +368,14 @@ "@babel/preset-env": "7.28.3", "@babel/runtime": "7.28.3", "@discoveryjs/json-ext": "0.6.3", - "@ngtools/webpack": "20.3.12", + "@ngtools/webpack": "20.3.25", "ansi-colors": "4.1.3", "autoprefixer": "10.4.21", "babel-loader": "10.0.0", "browserslist": "^4.21.5", - "copy-webpack-plugin": "13.0.1", + "copy-webpack-plugin": "14.0.0", "css-loader": "7.1.2", - "esbuild-wasm": "0.25.9", + "esbuild-wasm": "0.28.0", "fast-glob": "3.3.3", "http-proxy-middleware": "3.0.5", "istanbul-lib-instrument": "6.0.3", @@ -389,9 +388,9 @@ "mini-css-extract-plugin": "2.9.4", "open": "10.2.0", "ora": "8.2.0", - "picomatch": "4.0.3", + "picomatch": "4.0.4", "piscina": "5.1.3", - "postcss": "8.5.6", + "postcss": "8.5.12", "postcss-loader": "8.1.1", "resolve-url-loader": "5.0.0", "rxjs": "7.8.2", @@ -403,7 +402,7 @@ "terser": "5.43.1", "tree-kill": "1.2.2", "tslib": "2.8.1", - "webpack": "5.101.2", + "webpack": "5.105.0", "webpack-dev-middleware": "7.4.2", "webpack-dev-server": "5.2.2", "webpack-merge": "6.0.1", @@ -415,7 +414,7 @@ "yarn": ">= 1.13.0" }, "optionalDependencies": { - "esbuild": "0.25.9" + "esbuild": "0.28.0" }, "peerDependencies": { "@angular/compiler-cli": "^20.0.0", @@ -424,7 +423,7 @@ "@angular/platform-browser": "^20.0.0", "@angular/platform-server": "^20.0.0", "@angular/service-worker": "^20.0.0", - "@angular/ssr": "^20.3.12", + "@angular/ssr": "^20.3.25", "@web/test-runner": "^0.20.0", "browser-sync": "^3.0.2", "jest": "^29.5.0 || ^30.2.0", @@ -480,218 +479,499 @@ } } }, - "node_modules/@angular-devkit/build-angular/node_modules/@types/estree": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", - "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "node_modules/@angular-devkit/build-angular/node_modules/@esbuild/aix-ppc64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.0.tgz", + "integrity": "sha512-lhRUCeuOyJQURhTxl4WkpFTjIsbDayJHih5kZC1giwE+MhIzAb7mEsQMqMf18rHLsrb5qI1tafG20mLxEWcWlA==", + "cpu": [ + "ppc64" + ], "dev": true, - "license": "MIT" + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } }, - "node_modules/@angular-devkit/build-angular/node_modules/enhanced-resolve": { - "version": "5.19.0", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.19.0.tgz", - "integrity": "sha512-phv3E1Xl4tQOShqSte26C7Fl84EwUdZsyOuSSk9qtAGyyQs2s3jJzComh+Abf4g187lUUAvH+H26omrqia2aGg==", + "node_modules/@angular-devkit/build-angular/node_modules/@esbuild/android-arm": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.0.tgz", + "integrity": "sha512-wqh0ByljabXLKHeWXYLqoJ5jKC4XBaw6Hk08OfMrCRd2nP2ZQ5eleDZC41XHyCNgktBGYMbqnrJKq/K/lzPMSQ==", + "cpu": [ + "arm" + ], "dev": true, "license": "MIT", - "dependencies": { - "graceful-fs": "^4.2.4", - "tapable": "^2.3.0" - }, + "optional": true, + "os": [ + "android" + ], "engines": { - "node": ">=10.13.0" + "node": ">=18" } }, - "node_modules/@angular-devkit/build-angular/node_modules/es-module-lexer": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", - "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==", + "node_modules/@angular-devkit/build-angular/node_modules/@esbuild/android-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.0.tgz", + "integrity": "sha512-+WzIXQOSaGs33tLEgYPYe/yQHf0WTU0X42Jca3y8NWMbUVhp7rUnw+vAsRC/QiDrdD31IszMrZy+qwPOPjd+rw==", + "cpu": [ + "arm64" + ], "dev": true, - "license": "MIT" + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } }, - "node_modules/@angular-devkit/build-angular/node_modules/eslint-scope": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", - "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "node_modules/@angular-devkit/build-angular/node_modules/@esbuild/android-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.0.tgz", + "integrity": "sha512-+VJggoaKhk2VNNqVL7f6S189UzShHC/mR9EE8rDdSkdpN0KflSwWY/gWjDrNxxisg8Fp1ZCD9jLMo4m0OUfeUA==", + "cpu": [ + "x64" + ], "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^4.1.1" - }, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], "engines": { - "node": ">=8.0.0" + "node": ">=18" } }, - "node_modules/@angular-devkit/build-angular/node_modules/estraverse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", - "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "node_modules/@angular-devkit/build-angular/node_modules/@esbuild/darwin-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.0.tgz", + "integrity": "sha512-0T+A9WZm+bZ84nZBtk1ckYsOvyA3x7e2Acj1KdVfV4/2tdG4fzUp91YHx+GArWLtwqp77pBXVCPn2We7Letr0Q==", + "cpu": [ + "arm64" + ], "dev": true, - "license": "BSD-2-Clause", + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], "engines": { - "node": ">=4.0" + "node": ">=18" } }, - "node_modules/@angular-devkit/build-angular/node_modules/immutable": { - "version": "5.1.4", - "resolved": "https://registry.npmjs.org/immutable/-/immutable-5.1.4.tgz", - "integrity": "sha512-p6u1bG3YSnINT5RQmx/yRZBpenIl30kVxkTLDyHLIMk0gict704Q9n+thfDI7lTRm9vXdDYutVzXhzcThxTnXA==", + "node_modules/@angular-devkit/build-angular/node_modules/@esbuild/darwin-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.0.tgz", + "integrity": "sha512-fyzLm/DLDl/84OCfp2f/XQ4flmORsjU7VKt8HLjvIXChJoFFOIL6pLJPH4Yhd1n1gGFF9mPwtlN5Wf82DZs+LQ==", + "cpu": [ + "x64" + ], "dev": true, - "license": "MIT" + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } }, - "node_modules/@angular-devkit/build-angular/node_modules/json-parse-even-better-errors": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", - "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "node_modules/@angular-devkit/build-angular/node_modules/@esbuild/freebsd-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.0.tgz", + "integrity": "sha512-l9GeW5UZBT9k9brBYI+0WDffcRxgHQD8ShN2Ur4xWq/NFzUKm3k5lsH4PdaRgb2w7mI9u61nr2gI2mLI27Nh3Q==", + "cpu": [ + "arm64" + ], "dev": true, - "license": "MIT" + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } }, - "node_modules/@angular-devkit/build-angular/node_modules/rxjs": { - "version": "7.8.2", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz", - "integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==", + "node_modules/@angular-devkit/build-angular/node_modules/@esbuild/freebsd-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.0.tgz", + "integrity": "sha512-BXoQai/A0wPO6Es3yFJ7APCiKGc1tdAEOgeTNy3SsB491S3aHn4S4r3e976eUnPdU+NbdtmBuLncYir2tMU9Nw==", + "cpu": [ + "x64" + ], "dev": true, - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.1.0" + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" } }, - "node_modules/@angular-devkit/build-angular/node_modules/sass": { - "version": "1.90.0", - "resolved": "https://registry.npmjs.org/sass/-/sass-1.90.0.tgz", - "integrity": "sha512-9GUyuksjw70uNpb1MTYWsH9MQHOHY6kwfnkafC24+7aOMZn9+rVMBxRbLvw756mrBFbIsFg6Xw9IkR2Fnn3k+Q==", + "node_modules/@angular-devkit/build-angular/node_modules/@esbuild/linux-arm": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.0.tgz", + "integrity": "sha512-CjaaREJagqJp7iTaNQjjidaNbCKYcd4IDkzbwwxtSvjI7NZm79qiHc8HqciMddQ6CKvJT6aBd8lO9kN/ZudLlw==", + "cpu": [ + "arm" + ], "dev": true, "license": "MIT", - "dependencies": { - "chokidar": "^4.0.0", - "immutable": "^5.0.2", - "source-map-js": ">=0.6.2 <2.0.0" - }, - "bin": { - "sass": "sass.js" - }, + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=14.0.0" - }, - "optionalDependencies": { - "@parcel/watcher": "^2.4.1" + "node": ">=18" } }, - "node_modules/@angular-devkit/build-angular/node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "node_modules/@angular-devkit/build-angular/node_modules/@esbuild/linux-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.0.tgz", + "integrity": "sha512-RVyzfb3FWsGA55n6WY0MEIEPURL1FcbhFE6BffZEMEekfCzCIMtB5yyDcFnVbTnwk+CLAgTujmV/Lgvih56W+A==", + "cpu": [ + "arm64" + ], "dev": true, - "license": "0BSD" + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } }, - "node_modules/@angular-devkit/build-angular/node_modules/webpack": { - "version": "5.101.2", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.101.2.tgz", - "integrity": "sha512-4JLXU0tD6OZNVqlwzm3HGEhAHufSiyv+skb7q0d2367VDMzrU1Q/ZeepvkcHH0rZie6uqEtTQQe0OEOOluH3Mg==", + "node_modules/@angular-devkit/build-angular/node_modules/@esbuild/linux-ia32": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.0.tgz", + "integrity": "sha512-KBnSTt1kxl9x70q+ydterVdl+Cn0H18ngRMRCEQfrbqdUuntQQ0LoMZv47uB97NljZFzY6HcfqEZ2SAyIUTQBQ==", + "cpu": [ + "ia32" + ], "dev": true, "license": "MIT", - "dependencies": { - "@types/eslint-scope": "^3.7.7", - "@types/estree": "^1.0.8", - "@types/json-schema": "^7.0.15", - "@webassemblyjs/ast": "^1.14.1", - "@webassemblyjs/wasm-edit": "^1.14.1", - "@webassemblyjs/wasm-parser": "^1.14.1", - "acorn": "^8.15.0", - "acorn-import-phases": "^1.0.3", - "browserslist": "^4.24.0", - "chrome-trace-event": "^1.0.2", - "enhanced-resolve": "^5.17.3", - "es-module-lexer": "^1.2.1", - "eslint-scope": "5.1.1", - "events": "^3.2.0", - "glob-to-regexp": "^0.4.1", - "graceful-fs": "^4.2.11", - "json-parse-even-better-errors": "^2.3.1", - "loader-runner": "^4.2.0", - "mime-types": "^2.1.27", - "neo-async": "^2.6.2", - "schema-utils": "^4.3.2", - "tapable": "^2.1.1", - "terser-webpack-plugin": "^5.3.11", - "watchpack": "^2.4.1", - "webpack-sources": "^3.3.3" - }, - "bin": { - "webpack": "bin/webpack.js" - }, + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependenciesMeta": { - "webpack-cli": { - "optional": true - } + "node": ">=18" } }, - "node_modules/@angular-devkit/build-webpack": { - "version": "0.2003.12", - "resolved": "https://registry.npmjs.org/@angular-devkit/build-webpack/-/build-webpack-0.2003.12.tgz", - "integrity": "sha512-IkhCU0nAsXYBQOfHu2gQBcYBKhaV1c8wYtu7MmelBcN/iUrG8hRf1sZx+ppUgsdZuBYxCiDiLpcfRVRCIASkvw==", + "node_modules/@angular-devkit/build-angular/node_modules/@esbuild/linux-loong64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.0.tgz", + "integrity": "sha512-zpSlUce1mnxzgBADvxKXX5sl8aYQHo2ezvMNI8I0lbblJtp8V4odlm3Yzlj7gPyt3T8ReksE6bK+pT3WD+aJRg==", + "cpu": [ + "loong64" + ], "dev": true, "license": "MIT", - "dependencies": { - "@angular-devkit/architect": "0.2003.12", - "rxjs": "7.8.2" - }, + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": "^20.19.0 || ^22.12.0 || >=24.0.0", - "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", - "yarn": ">= 1.13.0" - }, - "peerDependencies": { - "webpack": "^5.30.0", - "webpack-dev-server": "^5.0.2" + "node": ">=18" } }, - "node_modules/@angular-devkit/build-webpack/node_modules/rxjs": { - "version": "7.8.2", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz", - "integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==", + "node_modules/@angular-devkit/build-angular/node_modules/@esbuild/linux-mips64el": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.0.tgz", + "integrity": "sha512-2jIfP6mmjkdmeTlsX/9vmdmhBmKADrWqN7zcdtHIeNSCH1SqIoNI63cYsjQR8J+wGa4Y5izRcSHSm8K3QWmk3w==", + "cpu": [ + "mips64el" + ], "dev": true, - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.1.0" + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" } }, - "node_modules/@angular-devkit/core": { - "version": "20.3.12", - "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-20.3.12.tgz", - "integrity": "sha512-ReFxd/UOoVDr3+kIUjmYILQZF89qg62POdY7a7OqBH7plmInFlYVSEDouJvGqj3LVCPiqTk2ZOSChbhS/eLxXA==", + "node_modules/@angular-devkit/build-angular/node_modules/@esbuild/linux-ppc64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.0.tgz", + "integrity": "sha512-bc0FE9wWeC0WBm49IQMPSPILRocGTQt3j5KPCA8os6VprfuJ7KD+5PzESSrJ6GmPIPJK965ZJHTUlSA6GNYEhg==", + "cpu": [ + "ppc64" + ], "dev": true, "license": "MIT", - "dependencies": { - "ajv": "8.17.1", - "ajv-formats": "3.0.1", - "jsonc-parser": "3.3.1", - "picomatch": "4.0.3", - "rxjs": "7.8.2", - "source-map": "0.7.6" - }, + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": "^20.19.0 || ^22.12.0 || >=24.0.0", - "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", - "yarn": ">= 1.13.0" + "node": ">=18" + } + }, + "node_modules/@angular-devkit/build-angular/node_modules/@esbuild/linux-riscv64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.0.tgz", + "integrity": "sha512-SQPZOwoTTT/HXFXQJG/vBX8sOFagGqvZyXcgLA3NhIqcBv1BJU1d46c0rGcrij2B56Z2rNiSLaZOYW5cUk7yLQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@angular-devkit/build-angular/node_modules/@esbuild/linux-s390x": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.0.tgz", + "integrity": "sha512-SCfR0HN8CEEjnYnySJTd2cw0k9OHB/YFzt5zgJEwa+wL/T/raGWYMBqwDNAC6dqFKmJYZoQBRfHjgwLHGSrn3Q==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@angular-devkit/build-angular/node_modules/@esbuild/linux-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.0.tgz", + "integrity": "sha512-us0dSb9iFxIi8srnpl931Nvs65it/Jd2a2K3qs7fz2WfGPHqzfzZTfec7oxZJRNPXPnNYZtanmRc4AL/JwVzHQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@angular-devkit/build-angular/node_modules/@esbuild/netbsd-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.0.tgz", + "integrity": "sha512-CR/RYotgtCKwtftMwJlUU7xCVNg3lMYZ0RzTmAHSfLCXw3NtZtNpswLEj/Kkf6kEL3Gw+BpOekRX0BYCtklhUw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@angular-devkit/build-angular/node_modules/@esbuild/netbsd-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.0.tgz", + "integrity": "sha512-nU1yhmYutL+fQ71Kxnhg8uEOdC0pwEW9entHykTgEbna2pw2dkbFSMeqjjyHZoCmt8SBkOSvV+yNmm94aUrrqw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@angular-devkit/build-angular/node_modules/@esbuild/openbsd-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.0.tgz", + "integrity": "sha512-cXb5vApOsRsxsEl4mcZ1XY3D4DzcoMxR/nnc4IyqYs0rTI8ZKmW6kyyg+11Z8yvgMfAEldKzP7AdP64HnSC/6g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@angular-devkit/build-angular/node_modules/@esbuild/openbsd-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.0.tgz", + "integrity": "sha512-8wZM2qqtv9UP3mzy7HiGYNH/zjTA355mpeuA+859TyR+e+Tc08IHYpLJuMsfpDJwoLo1ikIJI8jC3GFjnRClzA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@angular-devkit/build-angular/node_modules/@esbuild/openharmony-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.0.tgz", + "integrity": "sha512-FLGfyizszcef5C3YtoyQDACyg95+dndv79i2EekILBofh5wpCa1KuBqOWKrEHZg3zrL3t5ouE5jgr94vA+Wb2w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@angular-devkit/build-angular/node_modules/@esbuild/sunos-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.0.tgz", + "integrity": "sha512-1ZgjUoEdHZZl/YlV76TSCz9Hqj9h9YmMGAgAPYd+q4SicWNX3G5GCyx9uhQWSLcbvPW8Ni7lj4gDa1T40akdlw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@angular-devkit/build-angular/node_modules/@esbuild/win32-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.0.tgz", + "integrity": "sha512-Q9StnDmQ/enxnpxCCLSg0oo4+34B9TdXpuyPeTedN/6+iXBJ4J+zwfQI28u/Jl40nOYAxGoNi7mFP40RUtkmUA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@angular-devkit/build-angular/node_modules/@esbuild/win32-ia32": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.0.tgz", + "integrity": "sha512-zF3ag/gfiCe6U2iczcRzSYJKH1DCI+ByzSENHlM2FcDbEeo5Zd2C86Aq0tKUYAJJ1obRP84ymxIAksZUcdztHA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@angular-devkit/build-angular/node_modules/@esbuild/win32-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.0.tgz", + "integrity": "sha512-pEl1bO9mfAmIC+tW5btTmrKaujg3zGtUmWNdCw/xs70FBjwAL3o9OEKNHvNmnyylD6ubxUERiEhdsL0xBQ9efw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@angular-devkit/build-angular/node_modules/esbuild": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.0.tgz", + "integrity": "sha512-sNR9MHpXSUV/XB4zmsFKN+QgVG82Cc7+/aaxJ8Adi8hyOac+EXptIp45QBPaVyX3N70664wRbTcLTOemCAnyqw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "bin": { + "esbuild": "bin/esbuild" }, - "peerDependencies": { - "chokidar": "^4.0.0" + "engines": { + "node": ">=18" }, - "peerDependenciesMeta": { - "chokidar": { - "optional": true - } + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.28.0", + "@esbuild/android-arm": "0.28.0", + "@esbuild/android-arm64": "0.28.0", + "@esbuild/android-x64": "0.28.0", + "@esbuild/darwin-arm64": "0.28.0", + "@esbuild/darwin-x64": "0.28.0", + "@esbuild/freebsd-arm64": "0.28.0", + "@esbuild/freebsd-x64": "0.28.0", + "@esbuild/linux-arm": "0.28.0", + "@esbuild/linux-arm64": "0.28.0", + "@esbuild/linux-ia32": "0.28.0", + "@esbuild/linux-loong64": "0.28.0", + "@esbuild/linux-mips64el": "0.28.0", + "@esbuild/linux-ppc64": "0.28.0", + "@esbuild/linux-riscv64": "0.28.0", + "@esbuild/linux-s390x": "0.28.0", + "@esbuild/linux-x64": "0.28.0", + "@esbuild/netbsd-arm64": "0.28.0", + "@esbuild/netbsd-x64": "0.28.0", + "@esbuild/openbsd-arm64": "0.28.0", + "@esbuild/openbsd-x64": "0.28.0", + "@esbuild/openharmony-arm64": "0.28.0", + "@esbuild/sunos-x64": "0.28.0", + "@esbuild/win32-arm64": "0.28.0", + "@esbuild/win32-ia32": "0.28.0", + "@esbuild/win32-x64": "0.28.0" } }, - "node_modules/@angular-devkit/core/node_modules/rxjs": { + "node_modules/@angular-devkit/build-angular/node_modules/immutable": { + "version": "5.1.5", + "resolved": "https://registry.npmjs.org/immutable/-/immutable-5.1.5.tgz", + "integrity": "sha512-t7xcm2siw+hlUM68I+UEOK+z84RzmN59as9DZ7P1l0994DKUWV7UXBMQZVxaoMSRQ+PBZbHCOoBt7a2wxOMt+A==", + "dev": true, + "license": "MIT" + }, + "node_modules/@angular-devkit/build-angular/node_modules/rxjs": { "version": "7.8.2", "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz", "integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==", @@ -701,26 +981,65 @@ "tslib": "^2.1.0" } }, - "node_modules/@angular-devkit/schematics": { - "version": "20.3.25", - "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-20.3.25.tgz", - "integrity": "sha512-IB0IHf8ZRqr69hT/XIfHkYLPAYHWQ/WUc6+fKHBwq58jJlm/y5QUeTEuXvJ18IX/+hUIqw+E2Q3T0N9rLDLzXg==", + "node_modules/@angular-devkit/build-angular/node_modules/sass": { + "version": "1.90.0", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.90.0.tgz", + "integrity": "sha512-9GUyuksjw70uNpb1MTYWsH9MQHOHY6kwfnkafC24+7aOMZn9+rVMBxRbLvw756mrBFbIsFg6Xw9IkR2Fnn3k+Q==", "dev": true, "license": "MIT", "dependencies": { - "@angular-devkit/core": "20.3.25", - "jsonc-parser": "3.3.1", - "magic-string": "0.30.17", - "ora": "8.2.0", + "chokidar": "^4.0.0", + "immutable": "^5.0.2", + "source-map-js": ">=0.6.2 <2.0.0" + }, + "bin": { + "sass": "sass.js" + }, + "engines": { + "node": ">=14.0.0" + }, + "optionalDependencies": { + "@parcel/watcher": "^2.4.1" + } + }, + "node_modules/@angular-devkit/build-angular/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "license": "0BSD" + }, + "node_modules/@angular-devkit/build-webpack": { + "version": "0.2003.25", + "resolved": "https://registry.npmjs.org/@angular-devkit/build-webpack/-/build-webpack-0.2003.25.tgz", + "integrity": "sha512-jJMpYBdWeRfvrCna7JWsyMBbvjMcPblyzh4/pSfWw5znla3hJGzDtmb84qKZ+IB8eZNEcky4iGR5LK2jdGFLAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@angular-devkit/architect": "0.2003.25", "rxjs": "7.8.2" }, "engines": { "node": "^20.19.0 || ^22.12.0 || >=24.0.0", "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", "yarn": ">= 1.13.0" + }, + "peerDependencies": { + "webpack": "^5.30.0", + "webpack-dev-server": "^5.0.2" + } + }, + "node_modules/@angular-devkit/build-webpack/node_modules/rxjs": { + "version": "7.8.2", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz", + "integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.1.0" } }, - "node_modules/@angular-devkit/schematics/node_modules/@angular-devkit/core": { + "node_modules/@angular-devkit/core": { "version": "20.3.25", "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-20.3.25.tgz", "integrity": "sha512-pSfeWEoS1y9zxqTOw0Etj2NXkRmp/aU52wdnyq3KqVA9cJtgxtlOHlBBNswTil4dReojEy+0K1xJm+mLuWuLxA==", @@ -748,34 +1067,33 @@ } } }, - "node_modules/@angular-devkit/schematics/node_modules/ajv": { - "version": "8.18.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", - "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", + "node_modules/@angular-devkit/core/node_modules/rxjs": { + "version": "7.8.2", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz", + "integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==", "dev": true, - "license": "MIT", + "license": "Apache-2.0", "dependencies": { - "fast-deep-equal": "^3.1.3", - "fast-uri": "^3.0.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" + "tslib": "^2.1.0" } }, - "node_modules/@angular-devkit/schematics/node_modules/picomatch": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", - "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "node_modules/@angular-devkit/schematics": { + "version": "20.3.25", + "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-20.3.25.tgz", + "integrity": "sha512-IB0IHf8ZRqr69hT/XIfHkYLPAYHWQ/WUc6+fKHBwq58jJlm/y5QUeTEuXvJ18IX/+hUIqw+E2Q3T0N9rLDLzXg==", "dev": true, "license": "MIT", - "engines": { - "node": ">=12" + "dependencies": { + "@angular-devkit/core": "20.3.25", + "jsonc-parser": "3.3.1", + "magic-string": "0.30.17", + "ora": "8.2.0", + "rxjs": "7.8.2" }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" } }, "node_modules/@angular-devkit/schematics/node_modules/rxjs": { @@ -920,9 +1238,9 @@ } }, "node_modules/@angular/animations": { - "version": "20.3.14", - "resolved": "https://registry.npmjs.org/@angular/animations/-/animations-20.3.14.tgz", - "integrity": "sha512-Sx3/XNu2rR+R8T8JkJEaIpZDZPk0IecS0Ayt6HTanNUZXuw0HVou3vkjR5B2St5nM4MXs0gh+S6aLNuArtqJTQ==", + "version": "20.3.19", + "resolved": "https://registry.npmjs.org/@angular/animations/-/animations-20.3.19.tgz", + "integrity": "sha512-/FjU9i7J58/yBURhgVSIiLDcuyOfJxAa0b7ZrOsx6P+FES+M2T2BKZl5V2NuiP2fDFtjsV7U+M/Z9UNUmeHCEw==", "license": "MIT", "dependencies": { "tslib": "^2.3.0" @@ -931,18 +1249,18 @@ "node": "^20.19.0 || ^22.12.0 || >=24.0.0" }, "peerDependencies": { - "@angular/core": "20.3.14" + "@angular/core": "20.3.19" } }, "node_modules/@angular/build": { - "version": "20.3.12", - "resolved": "https://registry.npmjs.org/@angular/build/-/build-20.3.12.tgz", - "integrity": "sha512-iAZve4VPviC8y6RFctyh3qFXSlP5mth9K46/0zasB4LV4pcmu8BrzIHERxIn/jCDNdVdPh973kxo1ksO4WpyuA==", + "version": "20.3.25", + "resolved": "https://registry.npmjs.org/@angular/build/-/build-20.3.25.tgz", + "integrity": "sha512-ddWmPzYuzDWz9ql0262u9w3OJHXpSjHVNIDFIYOG2XYyMj/Xve0RkJ8/xvV+Hv7T3iqyyGKtsO+n/d0GCJQCtQ==", "dev": true, "license": "MIT", "dependencies": { "@ampproject/remapping": "2.3.0", - "@angular-devkit/architect": "0.2003.12", + "@angular-devkit/architect": "0.2003.25", "@babel/core": "7.28.3", "@babel/helper-annotate-as-pure": "7.27.3", "@babel/helper-split-export-declaration": "7.24.7", @@ -950,7 +1268,7 @@ "@vitejs/plugin-basic-ssl": "2.1.0", "beasties": "0.3.5", "browserslist": "^4.23.0", - "esbuild": "0.25.9", + "esbuild": "0.28.0", "https-proxy-agent": "7.0.6", "istanbul-lib-instrument": "6.0.3", "jsonc-parser": "3.3.1", @@ -958,14 +1276,14 @@ "magic-string": "0.30.17", "mrmime": "2.0.1", "parse5-html-rewriting-stream": "8.0.0", - "picomatch": "4.0.3", + "picomatch": "4.0.4", "piscina": "5.1.3", - "rollup": "4.52.3", + "rollup": "4.59.0", "sass": "1.90.0", "semver": "7.7.2", "source-map-support": "0.5.21", "tinyglobby": "0.2.14", - "vite": "7.1.11", + "vite": "7.3.2", "watchpack": "2.4.4" }, "engines": { @@ -984,7 +1302,7 @@ "@angular/platform-browser": "^20.0.0", "@angular/platform-server": "^20.0.0", "@angular/service-worker": "^20.0.0", - "@angular/ssr": "^20.3.12", + "@angular/ssr": "^20.3.25", "karma": "^6.4.0", "less": "^4.2.0", "ng-packagr": "^20.0.0", @@ -1033,803 +1351,948 @@ } } }, - "node_modules/@angular/build/node_modules/immutable": { - "version": "5.1.4", - "resolved": "https://registry.npmjs.org/immutable/-/immutable-5.1.4.tgz", - "integrity": "sha512-p6u1bG3YSnINT5RQmx/yRZBpenIl30kVxkTLDyHLIMk0gict704Q9n+thfDI7lTRm9vXdDYutVzXhzcThxTnXA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@angular/build/node_modules/sass": { - "version": "1.90.0", - "resolved": "https://registry.npmjs.org/sass/-/sass-1.90.0.tgz", - "integrity": "sha512-9GUyuksjw70uNpb1MTYWsH9MQHOHY6kwfnkafC24+7aOMZn9+rVMBxRbLvw756mrBFbIsFg6Xw9IkR2Fnn3k+Q==", + "node_modules/@angular/build/node_modules/@esbuild/aix-ppc64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.0.tgz", + "integrity": "sha512-lhRUCeuOyJQURhTxl4WkpFTjIsbDayJHih5kZC1giwE+MhIzAb7mEsQMqMf18rHLsrb5qI1tafG20mLxEWcWlA==", + "cpu": [ + "ppc64" + ], "dev": true, "license": "MIT", - "dependencies": { - "chokidar": "^4.0.0", - "immutable": "^5.0.2", - "source-map-js": ">=0.6.2 <2.0.0" - }, - "bin": { - "sass": "sass.js" - }, + "optional": true, + "os": [ + "aix" + ], "engines": { - "node": ">=14.0.0" - }, - "optionalDependencies": { - "@parcel/watcher": "^2.4.1" + "node": ">=18" } }, - "node_modules/@angular/cdk": { - "version": "20.2.14", - "resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-20.2.14.tgz", - "integrity": "sha512-7bZxc01URbiPiIBWThQ69XwOxVduqEKN4PhpbF2AAyfMc/W8Hcr4VoIJOwL0O1Nkq5beS8pCAqoOeIgFyXd/kg==", + "node_modules/@angular/build/node_modules/@esbuild/android-arm": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.0.tgz", + "integrity": "sha512-wqh0ByljabXLKHeWXYLqoJ5jKC4XBaw6Hk08OfMrCRd2nP2ZQ5eleDZC41XHyCNgktBGYMbqnrJKq/K/lzPMSQ==", + "cpu": [ + "arm" + ], + "dev": true, "license": "MIT", - "dependencies": { - "parse5": "^8.0.0", - "tslib": "^2.3.0" - }, - "peerDependencies": { - "@angular/common": "^20.0.0 || ^21.0.0", - "@angular/core": "^20.0.0 || ^21.0.0", - "rxjs": "^6.5.3 || ^7.4.0" + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" } }, - "node_modules/@angular/cli": { - "version": "20.3.12", - "resolved": "https://registry.npmjs.org/@angular/cli/-/cli-20.3.12.tgz", - "integrity": "sha512-vqVyVjbFPCRMjA5evL7tV2JeR6Anuzb9WcXTMB17fr7uzKNNAvo7KyRaOJjp+TU4JDARTNyGPy0aywfPx7R60A==", + "node_modules/@angular/build/node_modules/@esbuild/android-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.0.tgz", + "integrity": "sha512-+WzIXQOSaGs33tLEgYPYe/yQHf0WTU0X42Jca3y8NWMbUVhp7rUnw+vAsRC/QiDrdD31IszMrZy+qwPOPjd+rw==", + "cpu": [ + "arm64" + ], "dev": true, "license": "MIT", - "dependencies": { - "@angular-devkit/architect": "0.2003.12", - "@angular-devkit/core": "20.3.12", - "@angular-devkit/schematics": "20.3.12", - "@inquirer/prompts": "7.8.2", - "@listr2/prompt-adapter-inquirer": "3.0.1", - "@modelcontextprotocol/sdk": "1.17.3", - "@schematics/angular": "20.3.12", - "@yarnpkg/lockfile": "1.1.0", - "algoliasearch": "5.35.0", - "ini": "5.0.0", - "jsonc-parser": "3.3.1", - "listr2": "9.0.1", - "npm-package-arg": "13.0.0", - "pacote": "21.0.0", - "resolve": "1.22.10", - "semver": "7.7.2", - "yargs": "18.0.0", - "zod": "3.25.76" - }, - "bin": { - "ng": "bin/ng.js" - }, + "optional": true, + "os": [ + "android" + ], "engines": { - "node": "^20.19.0 || ^22.12.0 || >=24.0.0", - "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", - "yarn": ">= 1.13.0" + "node": ">=18" } }, - "node_modules/@angular/cli/node_modules/@angular-devkit/schematics": { - "version": "20.3.12", - "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-20.3.12.tgz", - "integrity": "sha512-JqJ1u59y+Ud51k/8MHYzSP+aQOeC2PJBaDmMnvqfWVaIt6n3x4gc/VtuhqhpJ0SKulbFuOWgAfI6QbPFrgUYQQ==", + "node_modules/@angular/build/node_modules/@esbuild/android-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.0.tgz", + "integrity": "sha512-+VJggoaKhk2VNNqVL7f6S189UzShHC/mR9EE8rDdSkdpN0KflSwWY/gWjDrNxxisg8Fp1ZCD9jLMo4m0OUfeUA==", + "cpu": [ + "x64" + ], "dev": true, "license": "MIT", - "dependencies": { - "@angular-devkit/core": "20.3.12", - "jsonc-parser": "3.3.1", - "magic-string": "0.30.17", - "ora": "8.2.0", - "rxjs": "7.8.2" - }, + "optional": true, + "os": [ + "android" + ], "engines": { - "node": "^20.19.0 || ^22.12.0 || >=24.0.0", - "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", - "yarn": ">= 1.13.0" + "node": ">=18" } }, - "node_modules/@angular/cli/node_modules/rxjs": { - "version": "7.8.2", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz", - "integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==", + "node_modules/@angular/build/node_modules/@esbuild/darwin-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.0.tgz", + "integrity": "sha512-0T+A9WZm+bZ84nZBtk1ckYsOvyA3x7e2Acj1KdVfV4/2tdG4fzUp91YHx+GArWLtwqp77pBXVCPn2We7Letr0Q==", + "cpu": [ + "arm64" + ], "dev": true, - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.1.0" + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" } }, - "node_modules/@angular/common": { - "version": "20.3.14", - "resolved": "https://registry.npmjs.org/@angular/common/-/common-20.3.14.tgz", - "integrity": "sha512-OOUvjTtnpktJLsNupA+GFT2q5zNocPdpOENA8aSrXvAheNybLjgi+otO3U3sQsvB1VwaoEZ9GT5O3lZlstnA/A==", + "node_modules/@angular/build/node_modules/@esbuild/darwin-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.0.tgz", + "integrity": "sha512-fyzLm/DLDl/84OCfp2f/XQ4flmORsjU7VKt8HLjvIXChJoFFOIL6pLJPH4Yhd1n1gGFF9mPwtlN5Wf82DZs+LQ==", + "cpu": [ + "x64" + ], + "dev": true, "license": "MIT", - "dependencies": { - "tslib": "^2.3.0" - }, + "optional": true, + "os": [ + "darwin" + ], "engines": { - "node": "^20.19.0 || ^22.12.0 || >=24.0.0" - }, - "peerDependencies": { - "@angular/core": "20.3.14", - "rxjs": "^6.5.3 || ^7.4.0" + "node": ">=18" } }, - "node_modules/@angular/compiler": { - "version": "20.3.14", - "resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-20.3.14.tgz", - "integrity": "sha512-KFbfPPAbclzGDujCVruflCD9j4Zwwxvrg7Y4C9GJYs3LZ85t+BfIMDDnvpBUM07ZLnfY4TO4gQdHmJAcaGGXDQ==", + "node_modules/@angular/build/node_modules/@esbuild/freebsd-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.0.tgz", + "integrity": "sha512-l9GeW5UZBT9k9brBYI+0WDffcRxgHQD8ShN2Ur4xWq/NFzUKm3k5lsH4PdaRgb2w7mI9u61nr2gI2mLI27Nh3Q==", + "cpu": [ + "arm64" + ], + "dev": true, "license": "MIT", - "dependencies": { - "tslib": "^2.3.0" - }, + "optional": true, + "os": [ + "freebsd" + ], "engines": { - "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + "node": ">=18" } }, - "node_modules/@angular/compiler-cli": { - "version": "20.3.14", - "resolved": "https://registry.npmjs.org/@angular/compiler-cli/-/compiler-cli-20.3.14.tgz", - "integrity": "sha512-lFg9ikwRClzDPjdFiwynbVFIi1RJZf/0i+OHa3Ns2gzXxJeHNKMJrHHjWZ2DU4N2UpxH0YAPe22N9Bie28IuQQ==", + "node_modules/@angular/build/node_modules/@esbuild/freebsd-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.0.tgz", + "integrity": "sha512-BXoQai/A0wPO6Es3yFJ7APCiKGc1tdAEOgeTNy3SsB491S3aHn4S4r3e976eUnPdU+NbdtmBuLncYir2tMU9Nw==", + "cpu": [ + "x64" + ], "dev": true, "license": "MIT", - "dependencies": { - "@babel/core": "7.28.3", - "@jridgewell/sourcemap-codec": "^1.4.14", - "chokidar": "^4.0.0", - "convert-source-map": "^1.5.1", - "reflect-metadata": "^0.2.0", - "semver": "^7.0.0", - "tslib": "^2.3.0", - "yargs": "^18.0.0" - }, - "bin": { - "ng-xi18n": "bundles/src/bin/ng_xi18n.js", - "ngc": "bundles/src/bin/ngc.js" - }, + "optional": true, + "os": [ + "freebsd" + ], "engines": { - "node": "^20.19.0 || ^22.12.0 || >=24.0.0" - }, - "peerDependencies": { - "@angular/compiler": "20.3.14", - "typescript": ">=5.8 <6.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "node": ">=18" } }, - "node_modules/@angular/compiler-cli/node_modules/reflect-metadata": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.2.2.tgz", - "integrity": "sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==", + "node_modules/@angular/build/node_modules/@esbuild/linux-arm": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.0.tgz", + "integrity": "sha512-CjaaREJagqJp7iTaNQjjidaNbCKYcd4IDkzbwwxtSvjI7NZm79qiHc8HqciMddQ6CKvJT6aBd8lO9kN/ZudLlw==", + "cpu": [ + "arm" + ], "dev": true, - "license": "Apache-2.0" - }, - "node_modules/@angular/core": { - "version": "20.3.14", - "resolved": "https://registry.npmjs.org/@angular/core/-/core-20.3.14.tgz", - "integrity": "sha512-rpyEbhWF6Fj/xI9IvNLZh5QBUYnoXuF7vX54CCtyQ2MHALxRR/aa1WRxjRM96cF2OqodQ/Gj3oYW8ei8hlBh4w==", "license": "MIT", - "dependencies": { - "tslib": "^2.3.0" - }, + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": "^20.19.0 || ^22.12.0 || >=24.0.0" - }, - "peerDependencies": { - "@angular/compiler": "20.3.14", - "rxjs": "^6.5.3 || ^7.4.0", - "zone.js": "~0.15.0" - }, - "peerDependenciesMeta": { - "@angular/compiler": { - "optional": true - }, - "zone.js": { - "optional": true - } + "node": ">=18" } }, - "node_modules/@angular/forms": { - "version": "20.3.14", - "resolved": "https://registry.npmjs.org/@angular/forms/-/forms-20.3.14.tgz", - "integrity": "sha512-fGrJ589tU+AKoxf+kaRrEw7wlSfVr1/z/Fz625ggFCc6ySQEityKW3JsnLfNkh5qGrdxib4BOfF78f9J7Pyk+w==", + "node_modules/@angular/build/node_modules/@esbuild/linux-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.0.tgz", + "integrity": "sha512-RVyzfb3FWsGA55n6WY0MEIEPURL1FcbhFE6BffZEMEekfCzCIMtB5yyDcFnVbTnwk+CLAgTujmV/Lgvih56W+A==", + "cpu": [ + "arm64" + ], + "dev": true, "license": "MIT", - "dependencies": { - "tslib": "^2.3.0" - }, + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": "^20.19.0 || ^22.12.0 || >=24.0.0" - }, - "peerDependencies": { - "@angular/common": "20.3.14", - "@angular/core": "20.3.14", - "@angular/platform-browser": "20.3.14", - "rxjs": "^6.5.3 || ^7.4.0" + "node": ">=18" } }, - "node_modules/@angular/language-service": { - "version": "20.3.14", - "resolved": "https://registry.npmjs.org/@angular/language-service/-/language-service-20.3.14.tgz", - "integrity": "sha512-3Jvi60WzLUe6jQJEw1xi/35uW7ynzxOS7iyZlwYfl2v8RljeLyyQsm0WNVpq6tXt80ppDeD59JvYTguEQ283Og==", + "node_modules/@angular/build/node_modules/@esbuild/linux-ia32": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.0.tgz", + "integrity": "sha512-KBnSTt1kxl9x70q+ydterVdl+Cn0H18ngRMRCEQfrbqdUuntQQ0LoMZv47uB97NljZFzY6HcfqEZ2SAyIUTQBQ==", + "cpu": [ + "ia32" + ], "dev": true, "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + "node": ">=18" } }, - "node_modules/@angular/material": { - "version": "20.2.14", - "resolved": "https://registry.npmjs.org/@angular/material/-/material-20.2.14.tgz", - "integrity": "sha512-IbAgV6XLsvmHiJzxycVhcNC1PA4M30qi+ERCOir6cT333Bxm8vDV32gsOjfL52uzG5YRARroPC+8s1XqR2oxeA==", - "license": "MIT", - "dependencies": { - "tslib": "^2.3.0" - }, - "peerDependencies": { - "@angular/cdk": "20.2.14", - "@angular/common": "^20.0.0 || ^21.0.0", - "@angular/core": "^20.0.0 || ^21.0.0", - "@angular/forms": "^20.0.0 || ^21.0.0", - "@angular/platform-browser": "^20.0.0 || ^21.0.0", - "rxjs": "^6.5.3 || ^7.4.0" - } - }, - "node_modules/@angular/platform-browser": { - "version": "20.3.14", - "resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-20.3.14.tgz", - "integrity": "sha512-Lviz9GfsIyOIBDal8QhIBKU8OMH29A0RhFw2opTC50sqKadXLN9CD7iSaAwQbNLc4mc3JAF4zth0AzKdHLbz7Q==", + "node_modules/@angular/build/node_modules/@esbuild/linux-loong64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.0.tgz", + "integrity": "sha512-zpSlUce1mnxzgBADvxKXX5sl8aYQHo2ezvMNI8I0lbblJtp8V4odlm3Yzlj7gPyt3T8ReksE6bK+pT3WD+aJRg==", + "cpu": [ + "loong64" + ], + "dev": true, "license": "MIT", - "dependencies": { - "tslib": "^2.3.0" - }, + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": "^20.19.0 || ^22.12.0 || >=24.0.0" - }, - "peerDependencies": { - "@angular/animations": "20.3.14", - "@angular/common": "20.3.14", - "@angular/core": "20.3.14" - }, - "peerDependenciesMeta": { - "@angular/animations": { - "optional": true - } + "node": ">=18" } }, - "node_modules/@angular/platform-browser-dynamic": { - "version": "20.3.14", - "resolved": "https://registry.npmjs.org/@angular/platform-browser-dynamic/-/platform-browser-dynamic-20.3.14.tgz", - "integrity": "sha512-g9z/g8gIOrBCX1SQ/GWwB0+JXBC6CKe0+yRyy9GGeBLm/YXWZHxTkmnDmueXXfPtUl8TOAInE22wlLcfunWTrg==", + "node_modules/@angular/build/node_modules/@esbuild/linux-mips64el": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.0.tgz", + "integrity": "sha512-2jIfP6mmjkdmeTlsX/9vmdmhBmKADrWqN7zcdtHIeNSCH1SqIoNI63cYsjQR8J+wGa4Y5izRcSHSm8K3QWmk3w==", + "cpu": [ + "mips64el" + ], + "dev": true, "license": "MIT", - "dependencies": { - "tslib": "^2.3.0" - }, + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": "^20.19.0 || ^22.12.0 || >=24.0.0" - }, - "peerDependencies": { - "@angular/common": "20.3.14", - "@angular/compiler": "20.3.14", - "@angular/core": "20.3.14", - "@angular/platform-browser": "20.3.14" + "node": ">=18" } }, - "node_modules/@angular/platform-server": { - "version": "20.3.14", - "resolved": "https://registry.npmjs.org/@angular/platform-server/-/platform-server-20.3.14.tgz", - "integrity": "sha512-CTc/K3AdOKjtU3PzK5cH8aRjpUZ2p7PVZ3JaVf9KMUHdRwNkPjMuRugoU4Adm5S3lGW4PsDuP49I6GkMsVDN6w==", + "node_modules/@angular/build/node_modules/@esbuild/linux-ppc64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.0.tgz", + "integrity": "sha512-bc0FE9wWeC0WBm49IQMPSPILRocGTQt3j5KPCA8os6VprfuJ7KD+5PzESSrJ6GmPIPJK965ZJHTUlSA6GNYEhg==", + "cpu": [ + "ppc64" + ], + "dev": true, "license": "MIT", - "dependencies": { - "tslib": "^2.3.0", - "xhr2": "^0.2.0" - }, + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": "^20.19.0 || ^22.12.0 || >=24.0.0" - }, - "peerDependencies": { - "@angular/common": "20.3.14", - "@angular/compiler": "20.3.14", - "@angular/core": "20.3.14", - "@angular/platform-browser": "20.3.14", - "rxjs": "^6.5.3 || ^7.4.0" + "node": ">=18" } }, - "node_modules/@angular/router": { - "version": "20.3.14", - "resolved": "https://registry.npmjs.org/@angular/router/-/router-20.3.14.tgz", - "integrity": "sha512-gi7/NuHRS9n9RCwh03VuVFizVMa2lKL/s+7yP3Ecq2nQ5uSeTMWb/91OmGEBwncI3wKPkYdQ9g3n6PvK/O8uDQ==", + "node_modules/@angular/build/node_modules/@esbuild/linux-riscv64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.0.tgz", + "integrity": "sha512-SQPZOwoTTT/HXFXQJG/vBX8sOFagGqvZyXcgLA3NhIqcBv1BJU1d46c0rGcrij2B56Z2rNiSLaZOYW5cUk7yLQ==", + "cpu": [ + "riscv64" + ], + "dev": true, "license": "MIT", - "dependencies": { - "tslib": "^2.3.0" - }, + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": "^20.19.0 || ^22.12.0 || >=24.0.0" - }, - "peerDependencies": { - "@angular/common": "20.3.14", - "@angular/core": "20.3.14", - "@angular/platform-browser": "20.3.14", - "rxjs": "^6.5.3 || ^7.4.0" + "node": ">=18" } }, - "node_modules/@angular/ssr": { - "version": "20.3.16", - "resolved": "https://registry.npmjs.org/@angular/ssr/-/ssr-20.3.16.tgz", - "integrity": "sha512-EpPSc9kUiLbe3Lpj0GUplt0JNPFmyuTnOv/h4bJqfj07xvSbn5vH3W0wl78RQrcOh9hfXua4xVCvCF/6nV6zPg==", + "node_modules/@angular/build/node_modules/@esbuild/linux-s390x": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.0.tgz", + "integrity": "sha512-SCfR0HN8CEEjnYnySJTd2cw0k9OHB/YFzt5zgJEwa+wL/T/raGWYMBqwDNAC6dqFKmJYZoQBRfHjgwLHGSrn3Q==", + "cpu": [ + "s390x" + ], + "dev": true, "license": "MIT", - "dependencies": { - "tslib": "^2.3.0" - }, - "peerDependencies": { - "@angular/common": "^20.0.0", - "@angular/core": "^20.0.0", - "@angular/platform-server": "^20.0.0", - "@angular/router": "^20.0.0" - }, - "peerDependenciesMeta": { - "@angular/platform-server": { - "optional": true - } + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" } }, - "node_modules/@babel/code-frame": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", - "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", + "node_modules/@angular/build/node_modules/@esbuild/linux-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.0.tgz", + "integrity": "sha512-us0dSb9iFxIi8srnpl931Nvs65it/Jd2a2K3qs7fz2WfGPHqzfzZTfec7oxZJRNPXPnNYZtanmRc4AL/JwVzHQ==", + "cpu": [ + "x64" + ], "dev": true, "license": "MIT", - "dependencies": { - "@babel/helper-validator-identifier": "^7.28.5", - "js-tokens": "^4.0.0", - "picocolors": "^1.1.1" - }, + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=6.9.0" + "node": ">=18" } }, - "node_modules/@babel/compat-data": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.0.tgz", - "integrity": "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==", + "node_modules/@angular/build/node_modules/@esbuild/netbsd-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.0.tgz", + "integrity": "sha512-CR/RYotgtCKwtftMwJlUU7xCVNg3lMYZ0RzTmAHSfLCXw3NtZtNpswLEj/Kkf6kEL3Gw+BpOekRX0BYCtklhUw==", + "cpu": [ + "arm64" + ], "dev": true, "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], "engines": { - "node": ">=6.9.0" + "node": ">=18" } }, - "node_modules/@babel/core": { - "version": "7.28.3", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.3.tgz", - "integrity": "sha512-yDBHV9kQNcr2/sUr9jghVyz9C3Y5G2zUM2H2lo+9mKv4sFgbA8s8Z9t8D1jiTkGoO/NoIfKMyKWr4s6CN23ZwQ==", + "node_modules/@angular/build/node_modules/@esbuild/netbsd-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.0.tgz", + "integrity": "sha512-nU1yhmYutL+fQ71Kxnhg8uEOdC0pwEW9entHykTgEbna2pw2dkbFSMeqjjyHZoCmt8SBkOSvV+yNmm94aUrrqw==", + "cpu": [ + "x64" + ], "dev": true, "license": "MIT", - "dependencies": { - "@ampproject/remapping": "^2.2.0", - "@babel/code-frame": "^7.27.1", - "@babel/generator": "^7.28.3", - "@babel/helper-compilation-targets": "^7.27.2", - "@babel/helper-module-transforms": "^7.28.3", - "@babel/helpers": "^7.28.3", - "@babel/parser": "^7.28.3", - "@babel/template": "^7.27.2", - "@babel/traverse": "^7.28.3", - "@babel/types": "^7.28.2", - "convert-source-map": "^2.0.0", - "debug": "^4.1.0", - "gensync": "^1.0.0-beta.2", - "json5": "^2.2.3", - "semver": "^6.3.1" - }, + "optional": true, + "os": [ + "netbsd" + ], "engines": { - "node": ">=6.9.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/babel" + "node": ">=18" } }, - "node_modules/@babel/core/node_modules/convert-source-map": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", - "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", - "dev": true, - "license": "MIT" - }, - "node_modules/@babel/core/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "node_modules/@angular/build/node_modules/@esbuild/openbsd-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.0.tgz", + "integrity": "sha512-cXb5vApOsRsxsEl4mcZ1XY3D4DzcoMxR/nnc4IyqYs0rTI8ZKmW6kyyg+11Z8yvgMfAEldKzP7AdP64HnSC/6g==", + "cpu": [ + "arm64" + ], "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" } }, - "node_modules/@babel/generator": { - "version": "7.28.3", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.3.tgz", - "integrity": "sha512-3lSpxGgvnmZznmBkCRnVREPUFJv2wrv9iAoFDvADJc0ypmdOxdUtcLeBgBJ6zE0PMeTKnxeQzyk0xTBq4Ep7zw==", + "node_modules/@angular/build/node_modules/@esbuild/openbsd-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.0.tgz", + "integrity": "sha512-8wZM2qqtv9UP3mzy7HiGYNH/zjTA355mpeuA+859TyR+e+Tc08IHYpLJuMsfpDJwoLo1ikIJI8jC3GFjnRClzA==", + "cpu": [ + "x64" + ], "dev": true, "license": "MIT", - "dependencies": { - "@babel/parser": "^7.28.3", - "@babel/types": "^7.28.2", - "@jridgewell/gen-mapping": "^0.3.12", - "@jridgewell/trace-mapping": "^0.3.28", - "jsesc": "^3.0.2" - }, + "optional": true, + "os": [ + "openbsd" + ], "engines": { - "node": ">=6.9.0" + "node": ">=18" } }, - "node_modules/@babel/helper-annotate-as-pure": { - "version": "7.27.3", - "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.27.3.tgz", - "integrity": "sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg==", + "node_modules/@angular/build/node_modules/@esbuild/openharmony-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.0.tgz", + "integrity": "sha512-FLGfyizszcef5C3YtoyQDACyg95+dndv79i2EekILBofh5wpCa1KuBqOWKrEHZg3zrL3t5ouE5jgr94vA+Wb2w==", + "cpu": [ + "arm64" + ], "dev": true, "license": "MIT", - "dependencies": { - "@babel/types": "^7.27.3" - }, + "optional": true, + "os": [ + "openharmony" + ], "engines": { - "node": ">=6.9.0" + "node": ">=18" } }, - "node_modules/@babel/helper-compilation-targets": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz", - "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==", + "node_modules/@angular/build/node_modules/@esbuild/sunos-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.0.tgz", + "integrity": "sha512-1ZgjUoEdHZZl/YlV76TSCz9Hqj9h9YmMGAgAPYd+q4SicWNX3G5GCyx9uhQWSLcbvPW8Ni7lj4gDa1T40akdlw==", + "cpu": [ + "x64" + ], "dev": true, "license": "MIT", - "dependencies": { - "@babel/compat-data": "^7.28.6", - "@babel/helper-validator-option": "^7.27.1", - "browserslist": "^4.24.0", - "lru-cache": "^5.1.1", - "semver": "^6.3.1" - }, + "optional": true, + "os": [ + "sunos" + ], "engines": { - "node": ">=6.9.0" + "node": ">=18" } }, - "node_modules/@babel/helper-compilation-targets/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "node_modules/@angular/build/node_modules/@esbuild/win32-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.0.tgz", + "integrity": "sha512-Q9StnDmQ/enxnpxCCLSg0oo4+34B9TdXpuyPeTedN/6+iXBJ4J+zwfQI28u/Jl40nOYAxGoNi7mFP40RUtkmUA==", + "cpu": [ + "arm64" + ], "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" } }, - "node_modules/@babel/helper-create-class-features-plugin": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.28.6.tgz", - "integrity": "sha512-dTOdvsjnG3xNT9Y0AUg1wAl38y+4Rl4sf9caSQZOXdNqVn+H+HbbJ4IyyHaIqNR6SW9oJpA/RuRjsjCw2IdIow==", + "node_modules/@angular/build/node_modules/@esbuild/win32-ia32": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.0.tgz", + "integrity": "sha512-zF3ag/gfiCe6U2iczcRzSYJKH1DCI+ByzSENHlM2FcDbEeo5Zd2C86Aq0tKUYAJJ1obRP84ymxIAksZUcdztHA==", + "cpu": [ + "ia32" + ], "dev": true, "license": "MIT", - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.27.3", - "@babel/helper-member-expression-to-functions": "^7.28.5", - "@babel/helper-optimise-call-expression": "^7.27.1", - "@babel/helper-replace-supers": "^7.28.6", - "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", - "@babel/traverse": "^7.28.6", - "semver": "^6.3.1" - }, + "optional": true, + "os": [ + "win32" + ], "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" + "node": ">=18" } }, - "node_modules/@babel/helper-create-class-features-plugin/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "node_modules/@angular/build/node_modules/@esbuild/win32-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.0.tgz", + "integrity": "sha512-pEl1bO9mfAmIC+tW5btTmrKaujg3zGtUmWNdCw/xs70FBjwAL3o9OEKNHvNmnyylD6ubxUERiEhdsL0xBQ9efw==", + "cpu": [ + "x64" + ], "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" } }, - "node_modules/@babel/helper-create-regexp-features-plugin": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.28.5.tgz", - "integrity": "sha512-N1EhvLtHzOvj7QQOUCCS3NrPJP8c5W6ZXCHDn7Yialuy1iu4r5EmIYkXlKNqT99Ciw+W0mDqWoR6HWMZlFP3hw==", + "node_modules/@angular/build/node_modules/esbuild": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.0.tgz", + "integrity": "sha512-sNR9MHpXSUV/XB4zmsFKN+QgVG82Cc7+/aaxJ8Adi8hyOac+EXptIp45QBPaVyX3N70664wRbTcLTOemCAnyqw==", "dev": true, + "hasInstallScript": true, "license": "MIT", - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.27.3", - "regexpu-core": "^6.3.1", - "semver": "^6.3.1" + "bin": { + "esbuild": "bin/esbuild" }, "engines": { - "node": ">=6.9.0" + "node": ">=18" }, - "peerDependencies": { - "@babel/core": "^7.0.0" + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.28.0", + "@esbuild/android-arm": "0.28.0", + "@esbuild/android-arm64": "0.28.0", + "@esbuild/android-x64": "0.28.0", + "@esbuild/darwin-arm64": "0.28.0", + "@esbuild/darwin-x64": "0.28.0", + "@esbuild/freebsd-arm64": "0.28.0", + "@esbuild/freebsd-x64": "0.28.0", + "@esbuild/linux-arm": "0.28.0", + "@esbuild/linux-arm64": "0.28.0", + "@esbuild/linux-ia32": "0.28.0", + "@esbuild/linux-loong64": "0.28.0", + "@esbuild/linux-mips64el": "0.28.0", + "@esbuild/linux-ppc64": "0.28.0", + "@esbuild/linux-riscv64": "0.28.0", + "@esbuild/linux-s390x": "0.28.0", + "@esbuild/linux-x64": "0.28.0", + "@esbuild/netbsd-arm64": "0.28.0", + "@esbuild/netbsd-x64": "0.28.0", + "@esbuild/openbsd-arm64": "0.28.0", + "@esbuild/openbsd-x64": "0.28.0", + "@esbuild/openharmony-arm64": "0.28.0", + "@esbuild/sunos-x64": "0.28.0", + "@esbuild/win32-arm64": "0.28.0", + "@esbuild/win32-ia32": "0.28.0", + "@esbuild/win32-x64": "0.28.0" } }, - "node_modules/@babel/helper-create-regexp-features-plugin/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "node_modules/@angular/build/node_modules/immutable": { + "version": "5.1.5", + "resolved": "https://registry.npmjs.org/immutable/-/immutable-5.1.5.tgz", + "integrity": "sha512-t7xcm2siw+hlUM68I+UEOK+z84RzmN59as9DZ7P1l0994DKUWV7UXBMQZVxaoMSRQ+PBZbHCOoBt7a2wxOMt+A==", "dev": true, - "license": "ISC", + "license": "MIT" + }, + "node_modules/@angular/build/node_modules/sass": { + "version": "1.90.0", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.90.0.tgz", + "integrity": "sha512-9GUyuksjw70uNpb1MTYWsH9MQHOHY6kwfnkafC24+7aOMZn9+rVMBxRbLvw756mrBFbIsFg6Xw9IkR2Fnn3k+Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "chokidar": "^4.0.0", + "immutable": "^5.0.2", + "source-map-js": ">=0.6.2 <2.0.0" + }, "bin": { - "semver": "bin/semver.js" + "sass": "sass.js" + }, + "engines": { + "node": ">=14.0.0" + }, + "optionalDependencies": { + "@parcel/watcher": "^2.4.1" } }, - "node_modules/@babel/helper-define-polyfill-provider": { - "version": "0.6.6", - "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.6.tgz", - "integrity": "sha512-mOAsxeeKkUKayvZR3HeTYD/fICpCPLJrU5ZjelT/PA6WHtNDBOE436YiaEUvHN454bRM3CebhDsIpieCc4texA==", - "dev": true, + "node_modules/@angular/cdk": { + "version": "20.2.14", + "resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-20.2.14.tgz", + "integrity": "sha512-7bZxc01URbiPiIBWThQ69XwOxVduqEKN4PhpbF2AAyfMc/W8Hcr4VoIJOwL0O1Nkq5beS8pCAqoOeIgFyXd/kg==", "license": "MIT", "dependencies": { - "@babel/helper-compilation-targets": "^7.28.6", - "@babel/helper-plugin-utils": "^7.28.6", - "debug": "^4.4.3", - "lodash.debounce": "^4.0.8", - "resolve": "^1.22.11" + "parse5": "^8.0.0", + "tslib": "^2.3.0" }, "peerDependencies": { - "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + "@angular/common": "^20.0.0 || ^21.0.0", + "@angular/core": "^20.0.0 || ^21.0.0", + "rxjs": "^6.5.3 || ^7.4.0" } }, - "node_modules/@babel/helper-define-polyfill-provider/node_modules/resolve": { - "version": "1.22.11", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz", - "integrity": "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==", + "node_modules/@angular/cli": { + "version": "20.3.25", + "resolved": "https://registry.npmjs.org/@angular/cli/-/cli-20.3.25.tgz", + "integrity": "sha512-QOSxza45CZY11kqPVpqsU+WGYF99rR/r9A9GykZQWuAHb5SEAxlXHyaaGMu/BMtBHy5HNIlJH25UlzelrbaNyQ==", "dev": true, "license": "MIT", "dependencies": { - "is-core-module": "^2.16.1", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" + "@angular-devkit/architect": "0.2003.25", + "@angular-devkit/core": "20.3.25", + "@angular-devkit/schematics": "20.3.25", + "@inquirer/prompts": "7.8.2", + "@listr2/prompt-adapter-inquirer": "3.0.1", + "@modelcontextprotocol/sdk": "1.26.0", + "@schematics/angular": "20.3.25", + "@yarnpkg/lockfile": "1.1.0", + "algoliasearch": "5.35.0", + "ini": "5.0.0", + "jsonc-parser": "3.3.1", + "listr2": "9.0.1", + "npm-package-arg": "13.0.0", + "pacote": "21.0.4", + "resolve": "1.22.10", + "semver": "7.7.2", + "yargs": "18.0.0", + "zod": "4.1.13" }, "bin": { - "resolve": "bin/resolve" + "ng": "bin/ng.js" }, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": "^20.19.0 || ^22.12.0 || >=24.0.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" } }, - "node_modules/@babel/helper-globals": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", - "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", - "dev": true, + "node_modules/@angular/common": { + "version": "20.3.19", + "resolved": "https://registry.npmjs.org/@angular/common/-/common-20.3.19.tgz", + "integrity": "sha512-hcB1eUEN8LGcKGc4DlRJ+abS6AYfbEHDZKg8LnXNugkbwI6Ebyh2AUYTDhzZL2S4aH+C8biHKgSYHFCqieCRhA==", "license": "MIT", + "dependencies": { + "tslib": "^2.3.0" + }, "engines": { - "node": ">=6.9.0" + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + }, + "peerDependencies": { + "@angular/core": "20.3.19", + "rxjs": "^6.5.3 || ^7.4.0" } }, - "node_modules/@babel/helper-member-expression-to-functions": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.28.5.tgz", - "integrity": "sha512-cwM7SBRZcPCLgl8a7cY0soT1SptSzAlMH39vwiRpOQkJlh53r5hdHwLSCZpQdVLT39sZt+CRpNwYG4Y2v77atg==", - "dev": true, + "node_modules/@angular/compiler": { + "version": "20.3.19", + "resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-20.3.19.tgz", + "integrity": "sha512-ETkgDKm0l2PuaBubgPJe0ccy8kE75DFu6/zKcz7TUuk3KrKF2OZAopbbjftsUSZGeCNvCdqHzjmcL6hQ6oAOwA==", "license": "MIT", "dependencies": { - "@babel/traverse": "^7.28.5", - "@babel/types": "^7.28.5" + "tslib": "^2.3.0" }, "engines": { - "node": ">=6.9.0" + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" } }, - "node_modules/@babel/helper-module-imports": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz", - "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==", + "node_modules/@angular/compiler-cli": { + "version": "20.3.19", + "resolved": "https://registry.npmjs.org/@angular/compiler-cli/-/compiler-cli-20.3.19.tgz", + "integrity": "sha512-ET/JjO8s62kAHfgIsGXlvW5VUwLqHm03q1y/2yD7aQW/WdDvssMsvZv7Knl440989vdOFemIGTMwVPakmWqRmA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/traverse": "^7.28.6", - "@babel/types": "^7.28.6" + "@babel/core": "7.28.3", + "@jridgewell/sourcemap-codec": "^1.4.14", + "chokidar": "^4.0.0", + "convert-source-map": "^1.5.1", + "reflect-metadata": "^0.2.0", + "semver": "^7.0.0", + "tslib": "^2.3.0", + "yargs": "^18.0.0" + }, + "bin": { + "ng-xi18n": "bundles/src/bin/ng_xi18n.js", + "ngc": "bundles/src/bin/ngc.js" }, "engines": { - "node": ">=6.9.0" + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + }, + "peerDependencies": { + "@angular/compiler": "20.3.19", + "typescript": ">=5.8 <6.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } } }, - "node_modules/@babel/helper-module-transforms": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz", - "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==", + "node_modules/@angular/compiler-cli/node_modules/reflect-metadata": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.2.2.tgz", + "integrity": "sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==", "dev": true, + "license": "Apache-2.0" + }, + "node_modules/@angular/core": { + "version": "20.3.19", + "resolved": "https://registry.npmjs.org/@angular/core/-/core-20.3.19.tgz", + "integrity": "sha512-SYnwW+q51bQoPtGFoGovm1P5GK9fMEXsG0lGaEAUapjskblAYyX7hLlM/jgueSojv2SjhqNF8aXR+gjHLhZVNA==", "license": "MIT", "dependencies": { - "@babel/helper-module-imports": "^7.28.6", - "@babel/helper-validator-identifier": "^7.28.5", - "@babel/traverse": "^7.28.6" + "tslib": "^2.3.0" }, "engines": { - "node": ">=6.9.0" + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" }, "peerDependencies": { - "@babel/core": "^7.0.0" + "@angular/compiler": "20.3.19", + "rxjs": "^6.5.3 || ^7.4.0", + "zone.js": "~0.15.0" + }, + "peerDependenciesMeta": { + "@angular/compiler": { + "optional": true + }, + "zone.js": { + "optional": true + } } }, - "node_modules/@babel/helper-optimise-call-expression": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.27.1.tgz", - "integrity": "sha512-URMGH08NzYFhubNSGJrpUEphGKQwMQYBySzat5cAByY1/YgIRkULnIy3tAMeszlL/so2HbeilYloUmSpd7GdVw==", - "dev": true, + "node_modules/@angular/forms": { + "version": "20.3.19", + "resolved": "https://registry.npmjs.org/@angular/forms/-/forms-20.3.19.tgz", + "integrity": "sha512-WJotd+Lhl4FG2b0K+aQNyQDHhR515zKCuphjiUqEW7sifWrOQxANLKzPBngGrH75ayANFgPaDf7U3ZRIoblcQA==", "license": "MIT", "dependencies": { - "@babel/types": "^7.27.1" + "tslib": "^2.3.0" }, "engines": { - "node": ">=6.9.0" + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + }, + "peerDependencies": { + "@angular/common": "20.3.19", + "@angular/core": "20.3.19", + "@angular/platform-browser": "20.3.19", + "rxjs": "^6.5.3 || ^7.4.0" } }, - "node_modules/@babel/helper-plugin-utils": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz", - "integrity": "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==", + "node_modules/@angular/language-service": { + "version": "20.3.19", + "resolved": "https://registry.npmjs.org/@angular/language-service/-/language-service-20.3.19.tgz", + "integrity": "sha512-9J0XrAKXInz11KKyNMrMZmn2NSjVbxzt/DsAumbrzzixeZwiY7vDy2Kqw/LLFLi7IlfMQ/gznz/mCVVgUWI5Gg==", "dev": true, "license": "MIT", "engines": { - "node": ">=6.9.0" + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" } }, - "node_modules/@babel/helper-remap-async-to-generator": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.27.1.tgz", - "integrity": "sha512-7fiA521aVw8lSPeI4ZOD3vRFkoqkJcS+z4hFo82bFSH/2tNd6eJ5qCVMS5OzDmZh/kaHQeBaeyxK6wljcPtveA==", - "dev": true, + "node_modules/@angular/material": { + "version": "20.2.14", + "resolved": "https://registry.npmjs.org/@angular/material/-/material-20.2.14.tgz", + "integrity": "sha512-IbAgV6XLsvmHiJzxycVhcNC1PA4M30qi+ERCOir6cT333Bxm8vDV32gsOjfL52uzG5YRARroPC+8s1XqR2oxeA==", "license": "MIT", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.27.1", - "@babel/helper-wrap-function": "^7.27.1", - "@babel/traverse": "^7.27.1" + "tslib": "^2.3.0" + }, + "peerDependencies": { + "@angular/cdk": "20.2.14", + "@angular/common": "^20.0.0 || ^21.0.0", + "@angular/core": "^20.0.0 || ^21.0.0", + "@angular/forms": "^20.0.0 || ^21.0.0", + "@angular/platform-browser": "^20.0.0 || ^21.0.0", + "rxjs": "^6.5.3 || ^7.4.0" + } + }, + "node_modules/@angular/platform-browser": { + "version": "20.3.19", + "resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-20.3.19.tgz", + "integrity": "sha512-TRZfatH1B/kreDwFRwtpLEurJQ6044qh6DWpvxzTbugaG5otLQJKTk+1z81/KsJwQqc1+24v+yuywc1LM7aq7w==", + "license": "MIT", + "dependencies": { + "tslib": "^2.3.0" }, "engines": { - "node": ">=6.9.0" + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" }, "peerDependencies": { - "@babel/core": "^7.0.0" + "@angular/animations": "20.3.19", + "@angular/common": "20.3.19", + "@angular/core": "20.3.19" + }, + "peerDependenciesMeta": { + "@angular/animations": { + "optional": true + } } }, - "node_modules/@babel/helper-replace-supers": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.28.6.tgz", - "integrity": "sha512-mq8e+laIk94/yFec3DxSjCRD2Z0TAjhVbEJY3UQrlwVo15Lmt7C2wAUbK4bjnTs4APkwsYLTahXRraQXhb1WCg==", - "dev": true, + "node_modules/@angular/platform-browser-dynamic": { + "version": "20.3.19", + "resolved": "https://registry.npmjs.org/@angular/platform-browser-dynamic/-/platform-browser-dynamic-20.3.19.tgz", + "integrity": "sha512-OgErw7wjcC+8yKF5h99hJq8x+tvc091wThfmdL5YC+U3HgRmUaNZFgB/jR7cb/NeeeC42QW5Vc0qoUTC9rMnLQ==", "license": "MIT", "dependencies": { - "@babel/helper-member-expression-to-functions": "^7.28.5", - "@babel/helper-optimise-call-expression": "^7.27.1", - "@babel/traverse": "^7.28.6" + "tslib": "^2.3.0" }, "engines": { - "node": ">=6.9.0" + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" }, "peerDependencies": { - "@babel/core": "^7.0.0" + "@angular/common": "20.3.19", + "@angular/compiler": "20.3.19", + "@angular/core": "20.3.19", + "@angular/platform-browser": "20.3.19" } }, - "node_modules/@babel/helper-skip-transparent-expression-wrappers": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.27.1.tgz", - "integrity": "sha512-Tub4ZKEXqbPjXgWLl2+3JpQAYBJ8+ikpQ2Ocj/q/r0LwE3UhENh7EUabyHjz2kCEsrRY83ew2DQdHluuiDQFzg==", - "dev": true, + "node_modules/@angular/platform-server": { + "version": "20.3.19", + "resolved": "https://registry.npmjs.org/@angular/platform-server/-/platform-server-20.3.19.tgz", + "integrity": "sha512-9STNB8Z5uYpaIgzfiJOH81c4CY2lM3oq/650+pdnjJsedxyEi+NAbnn5tF857Cd/N+43lR+OMolKgm0MJziHqw==", "license": "MIT", "dependencies": { - "@babel/traverse": "^7.27.1", - "@babel/types": "^7.27.1" + "tslib": "^2.3.0", + "xhr2": "^0.2.0" }, "engines": { - "node": ">=6.9.0" + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + }, + "peerDependencies": { + "@angular/common": "20.3.19", + "@angular/compiler": "20.3.19", + "@angular/core": "20.3.19", + "@angular/platform-browser": "20.3.19", + "rxjs": "^6.5.3 || ^7.4.0" } }, - "node_modules/@babel/helper-split-export-declaration": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.24.7.tgz", - "integrity": "sha512-oy5V7pD+UvfkEATUKvIjvIAH/xCzfsFVw7ygW2SI6NClZzquT+mwdTfgfdbUiceh6iQO0CHtCPsyze/MZ2YbAA==", - "dev": true, + "node_modules/@angular/router": { + "version": "20.3.19", + "resolved": "https://registry.npmjs.org/@angular/router/-/router-20.3.19.tgz", + "integrity": "sha512-qHrMniHOsCJ4neZmcQVodjutJilyXAXk7EhLa931QyL0qyVKVomv6E0I3UFzRaC3ZeHc+hzBdU6C6bvMFKTl1g==", "license": "MIT", "dependencies": { - "@babel/types": "^7.24.7" + "tslib": "^2.3.0" }, "engines": { - "node": ">=6.9.0" + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + }, + "peerDependencies": { + "@angular/common": "20.3.19", + "@angular/core": "20.3.19", + "@angular/platform-browser": "20.3.19", + "rxjs": "^6.5.3 || ^7.4.0" } }, - "node_modules/@babel/helper-string-parser": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", - "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "node_modules/@angular/ssr": { + "version": "20.3.25", + "resolved": "https://registry.npmjs.org/@angular/ssr/-/ssr-20.3.25.tgz", + "integrity": "sha512-A/lbXQ+GucLAQfCJ5img7/xuqlftWjJU+iJABq/ARDkOOE/rNHrxFkXjJBntj97+oXjr7HBYI2sxYRZCCEiEag==", + "license": "MIT", + "dependencies": { + "tslib": "^2.3.0" + }, + "peerDependencies": { + "@angular/common": "^20.0.0", + "@angular/core": "^20.0.0", + "@angular/platform-server": "^20.0.0", + "@angular/router": "^20.0.0" + }, + "peerDependenciesMeta": { + "@angular/platform-server": { + "optional": true + } + } + }, + "node_modules/@babel/code-frame": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", + "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", "dev": true, "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.28.5", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, "engines": { "node": ">=6.9.0" } }, - "node_modules/@babel/helper-validator-identifier": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", - "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "node_modules/@babel/compat-data": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.0.tgz", + "integrity": "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==", "dev": true, "license": "MIT", "engines": { "node": ">=6.9.0" } }, - "node_modules/@babel/helper-validator-option": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", - "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", + "node_modules/@babel/core": { + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.3.tgz", + "integrity": "sha512-yDBHV9kQNcr2/sUr9jghVyz9C3Y5G2zUM2H2lo+9mKv4sFgbA8s8Z9t8D1jiTkGoO/NoIfKMyKWr4s6CN23ZwQ==", "dev": true, "license": "MIT", + "dependencies": { + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.28.3", + "@babel/helper-compilation-targets": "^7.27.2", + "@babel/helper-module-transforms": "^7.28.3", + "@babel/helpers": "^7.28.3", + "@babel/parser": "^7.28.3", + "@babel/template": "^7.27.2", + "@babel/traverse": "^7.28.3", + "@babel/types": "^7.28.2", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, "engines": { "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" } }, - "node_modules/@babel/helper-wrap-function": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.28.6.tgz", - "integrity": "sha512-z+PwLziMNBeSQJonizz2AGnndLsP2DeGHIxDAn+wdHOGuo4Fo1x1HBPPXeE9TAOPHNNWQKCSlA2VZyYyyibDnQ==", + "node_modules/@babel/core/node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@babel/core/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/generator": { + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.3.tgz", + "integrity": "sha512-3lSpxGgvnmZznmBkCRnVREPUFJv2wrv9iAoFDvADJc0ypmdOxdUtcLeBgBJ6zE0PMeTKnxeQzyk0xTBq4Ep7zw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/template": "^7.28.6", - "@babel/traverse": "^7.28.6", - "@babel/types": "^7.28.6" + "@babel/parser": "^7.28.3", + "@babel/types": "^7.28.2", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" }, "engines": { "node": ">=6.9.0" } }, - "node_modules/@babel/helpers": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.6.tgz", - "integrity": "sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw==", + "node_modules/@babel/helper-annotate-as-pure": { + "version": "7.27.3", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.27.3.tgz", + "integrity": "sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/template": "^7.28.6", - "@babel/types": "^7.28.6" + "@babel/types": "^7.27.3" }, "engines": { "node": ">=6.9.0" } }, - "node_modules/@babel/parser": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.0.tgz", - "integrity": "sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww==", + "node_modules/@babel/helper-compilation-targets": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz", + "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/types": "^7.29.0" - }, - "bin": { - "parser": "bin/babel-parser.js" + "@babel/compat-data": "^7.28.6", + "@babel/helper-validator-option": "^7.27.1", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" }, "engines": { - "node": ">=6.0.0" + "node": ">=6.9.0" } }, - "node_modules/@babel/plugin-bugfix-firefox-class-in-computed-class-key": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.28.5.tgz", - "integrity": "sha512-87GDMS3tsmMSi/3bWOte1UblL+YUTFMV8SZPZ2eSEL17s74Cw/l63rR6NmGVKMYW2GYi85nE+/d6Hw5N0bEk2Q==", + "node_modules/@babel/helper-compilation-targets/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-create-class-features-plugin": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.28.6.tgz", + "integrity": "sha512-dTOdvsjnG3xNT9Y0AUg1wAl38y+4Rl4sf9caSQZOXdNqVn+H+HbbJ4IyyHaIqNR6SW9oJpA/RuRjsjCw2IdIow==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/traverse": "^7.28.5" + "@babel/helper-annotate-as-pure": "^7.27.3", + "@babel/helper-member-expression-to-functions": "^7.28.5", + "@babel/helper-optimise-call-expression": "^7.27.1", + "@babel/helper-replace-supers": "^7.28.6", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", + "@babel/traverse": "^7.28.6", + "semver": "^6.3.1" }, "engines": { "node": ">=6.9.0" @@ -1838,14 +2301,26 @@ "@babel/core": "^7.0.0" } }, - "node_modules/@babel/plugin-bugfix-safari-class-field-initializer-scope": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-class-field-initializer-scope/-/plugin-bugfix-safari-class-field-initializer-scope-7.27.1.tgz", - "integrity": "sha512-qNeq3bCKnGgLkEXUuFry6dPlGfCdQNZbn7yUAPCInwAJHMU7THJfrBSozkcWq5sNM6RcF3S8XyQL2A52KNR9IA==", + "node_modules/@babel/helper-create-class-features-plugin/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-create-regexp-features-plugin": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.28.5.tgz", + "integrity": "sha512-N1EhvLtHzOvj7QQOUCCS3NrPJP8c5W6ZXCHDn7Yialuy1iu4r5EmIYkXlKNqT99Ciw+W0mDqWoR6HWMZlFP3hw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-annotate-as-pure": "^7.27.3", + "regexpu-core": "^6.3.1", + "semver": "^6.3.1" }, "engines": { "node": ">=6.9.0" @@ -1854,111 +2329,102 @@ "@babel/core": "^7.0.0" } }, - "node_modules/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.27.1.tgz", - "integrity": "sha512-g4L7OYun04N1WyqMNjldFwlfPCLVkgB54A/YCXICZYBsvJJE3kByKv9c9+R/nAfmIfjl2rKYLNyMHboYbZaWaA==", + "node_modules/@babel/helper-create-regexp-features-plugin/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" + "license": "ISC", + "bin": { + "semver": "bin/semver.js" } }, - "node_modules/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.27.1.tgz", - "integrity": "sha512-oO02gcONcD5O1iTLi/6frMJBIwWEHceWGSGqrpCmEL8nogiS6J9PBlE48CaK20/Jx1LuRml9aDftLgdjXT8+Cw==", + "node_modules/@babel/helper-define-polyfill-provider": { + "version": "0.6.6", + "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.6.tgz", + "integrity": "sha512-mOAsxeeKkUKayvZR3HeTYD/fICpCPLJrU5ZjelT/PA6WHtNDBOE436YiaEUvHN454bRM3CebhDsIpieCc4texA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", - "@babel/plugin-transform-optional-chaining": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" + "@babel/helper-compilation-targets": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6", + "debug": "^4.4.3", + "lodash.debounce": "^4.0.8", + "resolve": "^1.22.11" }, "peerDependencies": { - "@babel/core": "^7.13.0" + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, - "node_modules/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.28.6.tgz", - "integrity": "sha512-a0aBScVTlNaiUe35UtfxAN7A/tehvvG4/ByO6+46VPKTRSlfnAFsgKy0FUh+qAkQrDTmhDkT+IBOKlOoMUxQ0g==", + "node_modules/@babel/helper-define-polyfill-provider/node_modules/resolve": { + "version": "1.22.11", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz", + "integrity": "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.28.6", - "@babel/traverse": "^7.28.6" + "is-core-module": "^2.16.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" }, "engines": { - "node": ">=6.9.0" + "node": ">= 0.4" }, - "peerDependencies": { - "@babel/core": "^7.0.0" + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/@babel/plugin-proposal-private-property-in-object": { - "version": "7.21.0-placeholder-for-preset-env.2", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz", - "integrity": "sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==", + "node_modules/@babel/helper-globals": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", + "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", "dev": true, "license": "MIT", "engines": { "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-syntax-import-assertions": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.28.6.tgz", - "integrity": "sha512-pSJUpFHdx9z5nqTSirOCMtYVP2wFgoWhP0p3g8ONK/4IHhLIBd0B9NYqAvIUAhq+OkhO4VM1tENCt0cjlsNShw==", + "node_modules/@babel/helper-member-expression-to-functions": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.28.5.tgz", + "integrity": "sha512-cwM7SBRZcPCLgl8a7cY0soT1SptSzAlMH39vwiRpOQkJlh53r5hdHwLSCZpQdVLT39sZt+CRpNwYG4Y2v77atg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.28.6" + "@babel/traverse": "^7.28.5", + "@babel/types": "^7.28.5" }, "engines": { "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-syntax-import-attributes": { + "node_modules/@babel/helper-module-imports": { "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.28.6.tgz", - "integrity": "sha512-jiLC0ma9XkQT3TKJ9uYvlakm66Pamywo+qwL+oL8HJOvc6TWdZXVfhqJr8CCzbSGUAbDOzlGHJC1U+vRfLQDvw==", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz", + "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.28.6" + "@babel/traverse": "^7.28.6", + "@babel/types": "^7.28.6" }, "engines": { "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-syntax-unicode-sets-regex": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-unicode-sets-regex/-/plugin-syntax-unicode-sets-regex-7.18.6.tgz", - "integrity": "sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg==", + "node_modules/@babel/helper-module-transforms": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz", + "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6" + "@babel/helper-module-imports": "^7.28.6", + "@babel/helper-validator-identifier": "^7.28.5", + "@babel/traverse": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -1967,221 +2433,192 @@ "@babel/core": "^7.0.0" } }, - "node_modules/@babel/plugin-transform-arrow-functions": { + "node_modules/@babel/helper-optimise-call-expression": { "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.27.1.tgz", - "integrity": "sha512-8Z4TGic6xW70FKThA5HYEKKyBpOOsucTOD1DjU3fZxDg+K3zBJcXMFnt/4yQiZnf5+MiOMSXQ9PaEK/Ilh1DeA==", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.27.1.tgz", + "integrity": "sha512-URMGH08NzYFhubNSGJrpUEphGKQwMQYBySzat5cAByY1/YgIRkULnIy3tAMeszlL/so2HbeilYloUmSpd7GdVw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/types": "^7.27.1" }, "engines": { "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-async-generator-functions": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.28.0.tgz", - "integrity": "sha512-BEOdvX4+M765icNPZeidyADIvQ1m1gmunXufXxvRESy/jNNyfovIqUyE7MVgGBjWktCoJlzvFA1To2O4ymIO3Q==", + "node_modules/@babel/helper-plugin-utils": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz", + "integrity": "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==", "dev": true, "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/helper-remap-async-to-generator": "^7.27.1", - "@babel/traverse": "^7.28.0" - }, "engines": { "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-async-to-generator": { + "node_modules/@babel/helper-remap-async-to-generator": { "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.27.1.tgz", - "integrity": "sha512-NREkZsZVJS4xmTr8qzE5y8AfIPqsdQfRuUiLRTEzb7Qii8iFWCyDKaUV2c0rCuh4ljDZ98ALHP/PetiBV2nddA==", + "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.27.1.tgz", + "integrity": "sha512-7fiA521aVw8lSPeI4ZOD3vRFkoqkJcS+z4hFo82bFSH/2tNd6eJ5qCVMS5OzDmZh/kaHQeBaeyxK6wljcPtveA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-module-imports": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/helper-remap-async-to-generator": "^7.27.1" + "@babel/helper-annotate-as-pure": "^7.27.1", + "@babel/helper-wrap-function": "^7.27.1", + "@babel/traverse": "^7.27.1" }, "engines": { "node": ">=6.9.0" }, "peerDependencies": { - "@babel/core": "^7.0.0-0" + "@babel/core": "^7.0.0" } }, - "node_modules/@babel/plugin-transform-block-scoped-functions": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.27.1.tgz", - "integrity": "sha512-cnqkuOtZLapWYZUYM5rVIdv1nXYuFVIltZ6ZJ7nIj585QsjKM5dhL2Fu/lICXZ1OyIAFc7Qy+bvDAtTXqGrlhg==", + "node_modules/@babel/helper-replace-supers": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.28.6.tgz", + "integrity": "sha512-mq8e+laIk94/yFec3DxSjCRD2Z0TAjhVbEJY3UQrlwVo15Lmt7C2wAUbK4bjnTs4APkwsYLTahXRraQXhb1WCg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-member-expression-to-functions": "^7.28.5", + "@babel/helper-optimise-call-expression": "^7.27.1", + "@babel/traverse": "^7.28.6" }, "engines": { "node": ">=6.9.0" }, "peerDependencies": { - "@babel/core": "^7.0.0-0" + "@babel/core": "^7.0.0" } }, - "node_modules/@babel/plugin-transform-block-scoping": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.28.6.tgz", - "integrity": "sha512-tt/7wOtBmwHPNMPu7ax4pdPz6shjFrmHDghvNC+FG9Qvj7D6mJcoRQIF5dy4njmxR941l6rgtvfSB2zX3VlUIw==", + "node_modules/@babel/helper-skip-transparent-expression-wrappers": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.27.1.tgz", + "integrity": "sha512-Tub4ZKEXqbPjXgWLl2+3JpQAYBJ8+ikpQ2Ocj/q/r0LwE3UhENh7EUabyHjz2kCEsrRY83ew2DQdHluuiDQFzg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.28.6" + "@babel/traverse": "^7.27.1", + "@babel/types": "^7.27.1" }, "engines": { "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-class-properties": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.28.6.tgz", - "integrity": "sha512-dY2wS3I2G7D697VHndN91TJr8/AAfXQNt5ynCTI/MpxMsSzHp+52uNivYT5wCPax3whc47DR8Ba7cmlQMg24bw==", + "node_modules/@babel/helper-split-export-declaration": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.24.7.tgz", + "integrity": "sha512-oy5V7pD+UvfkEATUKvIjvIAH/xCzfsFVw7ygW2SI6NClZzquT+mwdTfgfdbUiceh6iQO0CHtCPsyze/MZ2YbAA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.28.6", - "@babel/helper-plugin-utils": "^7.28.6" + "@babel/types": "^7.24.7" }, "engines": { "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-class-static-block": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.28.6.tgz", - "integrity": "sha512-rfQ++ghVwTWTqQ7w8qyDxL1XGihjBss4CmTgGRCTAC9RIbhVpyp4fOeZtta0Lbf+dTNIVJer6ych2ibHwkZqsQ==", + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", "dev": true, "license": "MIT", - "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.28.6", - "@babel/helper-plugin-utils": "^7.28.6" - }, "engines": { "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.12.0" } }, - "node_modules/@babel/plugin-transform-classes": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.28.6.tgz", - "integrity": "sha512-EF5KONAqC5zAqT783iMGuM2ZtmEBy+mJMOKl2BCvPZ2lVrwvXnB6o+OBWCS+CoeCCpVRF2sA2RBKUxvT8tQT5Q==", + "node_modules/@babel/helper-validator-identifier": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", "dev": true, "license": "MIT", - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.27.3", - "@babel/helper-compilation-targets": "^7.28.6", - "@babel/helper-globals": "^7.28.0", - "@babel/helper-plugin-utils": "^7.28.6", - "@babel/helper-replace-supers": "^7.28.6", - "@babel/traverse": "^7.28.6" - }, "engines": { "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-computed-properties": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.28.6.tgz", - "integrity": "sha512-bcc3k0ijhHbc2lEfpFHgx7eYw9KNXqOerKWfzbxEHUGKnS3sz9C4CNL9OiFN1297bDNfUiSO7DaLzbvHQQQ1BQ==", + "node_modules/@babel/helper-validator-option": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", + "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", "dev": true, "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.28.6", - "@babel/template": "^7.28.6" - }, "engines": { "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-destructuring": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.28.5.tgz", - "integrity": "sha512-Kl9Bc6D0zTUcFUvkNuQh4eGXPKKNDOJQXVyyM4ZAQPMveniJdxi8XMJwLo+xSoW3MIq81bD33lcUe9kZpl0MCw==", + "node_modules/@babel/helper-wrap-function": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.28.6.tgz", + "integrity": "sha512-z+PwLziMNBeSQJonizz2AGnndLsP2DeGHIxDAn+wdHOGuo4Fo1x1HBPPXeE9TAOPHNNWQKCSlA2VZyYyyibDnQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/traverse": "^7.28.5" + "@babel/template": "^7.28.6", + "@babel/traverse": "^7.28.6", + "@babel/types": "^7.28.6" }, "engines": { "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-dotall-regex": { + "node_modules/@babel/helpers": { "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.28.6.tgz", - "integrity": "sha512-SljjowuNKB7q5Oayv4FoPzeB74g3QgLt8IVJw9ADvWy3QnUb/01aw8I4AVv8wYnPvQz2GDDZ/g3GhcNyDBI4Bg==", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.6.tgz", + "integrity": "sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.28.5", - "@babel/helper-plugin-utils": "^7.28.6" + "@babel/template": "^7.28.6", + "@babel/types": "^7.28.6" }, "engines": { "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.0.tgz", + "integrity": "sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.0" }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" } }, - "node_modules/@babel/plugin-transform-duplicate-keys": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.27.1.tgz", - "integrity": "sha512-MTyJk98sHvSs+cvZ4nOauwTTG1JeonDjSGvGGUNHreGQns+Mpt6WX/dVzWBHgg+dYZhkC4X+zTDfkTU+Vy9y7Q==", + "node_modules/@babel/plugin-bugfix-firefox-class-in-computed-class-key": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.28.5.tgz", + "integrity": "sha512-87GDMS3tsmMSi/3bWOte1UblL+YUTFMV8SZPZ2eSEL17s74Cw/l63rR6NmGVKMYW2GYi85nE+/d6Hw5N0bEk2Q==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/traverse": "^7.28.5" }, "engines": { "node": ">=6.9.0" }, "peerDependencies": { - "@babel/core": "^7.0.0-0" + "@babel/core": "^7.0.0" } }, - "node_modules/@babel/plugin-transform-duplicate-named-capturing-groups-regex": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-named-capturing-groups-regex/-/plugin-transform-duplicate-named-capturing-groups-regex-7.29.0.tgz", - "integrity": "sha512-zBPcW2lFGxdiD8PUnPwJjag2J9otbcLQzvbiOzDxpYXyCuYX9agOwMPGn1prVH0a4qzhCKu24rlH4c1f7yA8rw==", + "node_modules/@babel/plugin-bugfix-safari-class-field-initializer-scope": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-class-field-initializer-scope/-/plugin-bugfix-safari-class-field-initializer-scope-7.27.1.tgz", + "integrity": "sha512-qNeq3bCKnGgLkEXUuFry6dPlGfCdQNZbn7yUAPCInwAJHMU7THJfrBSozkcWq5sNM6RcF3S8XyQL2A52KNR9IA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.28.5", - "@babel/helper-plugin-utils": "^7.28.6" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -2190,10 +2627,10 @@ "@babel/core": "^7.0.0" } }, - "node_modules/@babel/plugin-transform-dynamic-import": { + "node_modules/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.27.1.tgz", - "integrity": "sha512-MHzkWQcEmjzzVW9j2q8LGjwGWpG2mjwaaB0BNQwst3FIjqsg8Ct/mIZlvSPJvfi9y2AC8mi/ktxbFVL9pZ1I4A==", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.27.1.tgz", + "integrity": "sha512-g4L7OYun04N1WyqMNjldFwlfPCLVkgB54A/YCXICZYBsvJJE3kByKv9c9+R/nAfmIfjl2rKYLNyMHboYbZaWaA==", "dev": true, "license": "MIT", "dependencies": { @@ -2203,51 +2640,50 @@ "node": ">=6.9.0" }, "peerDependencies": { - "@babel/core": "^7.0.0-0" + "@babel/core": "^7.0.0" } }, - "node_modules/@babel/plugin-transform-explicit-resource-management": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-explicit-resource-management/-/plugin-transform-explicit-resource-management-7.28.6.tgz", - "integrity": "sha512-Iao5Konzx2b6g7EPqTy40UZbcdXE126tTxVFr/nAIj+WItNxjKSYTEw3RC+A2/ZetmdJsgueL1KhaMCQHkLPIg==", + "node_modules/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.27.1.tgz", + "integrity": "sha512-oO02gcONcD5O1iTLi/6frMJBIwWEHceWGSGqrpCmEL8nogiS6J9PBlE48CaK20/Jx1LuRml9aDftLgdjXT8+Cw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.28.6", - "@babel/plugin-transform-destructuring": "^7.28.5" + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", + "@babel/plugin-transform-optional-chaining": "^7.27.1" }, "engines": { "node": ">=6.9.0" }, "peerDependencies": { - "@babel/core": "^7.0.0-0" + "@babel/core": "^7.13.0" } }, - "node_modules/@babel/plugin-transform-exponentiation-operator": { + "node_modules/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": { "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.28.6.tgz", - "integrity": "sha512-WitabqiGjV/vJ0aPOLSFfNY1u9U3R7W36B03r5I2KoNix+a3sOhJ3pKFB3R5It9/UiK78NiO0KE9P21cMhlPkw==", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.28.6.tgz", + "integrity": "sha512-a0aBScVTlNaiUe35UtfxAN7A/tehvvG4/ByO6+46VPKTRSlfnAFsgKy0FUh+qAkQrDTmhDkT+IBOKlOoMUxQ0g==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.28.6" + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/traverse": "^7.28.6" }, "engines": { "node": ">=6.9.0" }, "peerDependencies": { - "@babel/core": "^7.0.0-0" + "@babel/core": "^7.0.0" } }, - "node_modules/@babel/plugin-transform-export-namespace-from": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.27.1.tgz", - "integrity": "sha512-tQvHWSZ3/jH2xuq/vZDy0jNn+ZdXJeM8gHvX4lnJmsc3+50yPlWdZXIc5ay+umX+2/tJIqHqiEqcJvxlmIvRvQ==", + "node_modules/@babel/plugin-proposal-private-property-in-object": { + "version": "7.21.0-placeholder-for-preset-env.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz", + "integrity": "sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==", "dev": true, "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, "engines": { "node": ">=6.9.0" }, @@ -2255,15 +2691,14 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-for-of": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.27.1.tgz", - "integrity": "sha512-BfbWFFEJFQzLCQ5N8VocnCtA8J1CLkNTe2Ms2wocj75dd6VpiqS5Z5quTYcUoo4Yq+DN0rtikODccuv7RU81sw==", + "node_modules/@babel/plugin-syntax-import-assertions": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.28.6.tgz", + "integrity": "sha512-pSJUpFHdx9z5nqTSirOCMtYVP2wFgoWhP0p3g8ONK/4IHhLIBd0B9NYqAvIUAhq+OkhO4VM1tENCt0cjlsNShw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -2272,16 +2707,14 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-function-name": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.27.1.tgz", - "integrity": "sha512-1bQeydJF9Nr1eBCMMbC+hdwmRlsv5XYOMu03YSWFwNs0HsAmtSxxF1fyuYPqemVldVyFmlCU7w8UE14LupUSZQ==", + "node_modules/@babel/plugin-syntax-import-attributes": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.28.6.tgz", + "integrity": "sha512-jiLC0ma9XkQT3TKJ9uYvlakm66Pamywo+qwL+oL8HJOvc6TWdZXVfhqJr8CCzbSGUAbDOzlGHJC1U+vRfLQDvw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-compilation-targets": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/traverse": "^7.27.1" + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -2290,26 +2723,27 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-json-strings": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.28.6.tgz", - "integrity": "sha512-Nr+hEN+0geQkzhbdgQVPoqr47lZbm+5fCUmO70722xJZd0Mvb59+33QLImGj6F+DkK3xgDi1YVysP8whD6FQAw==", + "node_modules/@babel/plugin-syntax-unicode-sets-regex": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-unicode-sets-regex/-/plugin-syntax-unicode-sets-regex-7.18.6.tgz", + "integrity": "sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.28.6" + "@babel/helper-create-regexp-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" }, "engines": { "node": ">=6.9.0" }, "peerDependencies": { - "@babel/core": "^7.0.0-0" + "@babel/core": "^7.0.0" } }, - "node_modules/@babel/plugin-transform-literals": { + "node_modules/@babel/plugin-transform-arrow-functions": { "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.27.1.tgz", - "integrity": "sha512-0HCFSepIpLTkLcsi86GG3mTUzxV5jpmbv97hTETW3yzrAij8aqlD36toB1D0daVFJM8NK6GvKO0gslVQmm+zZA==", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.27.1.tgz", + "integrity": "sha512-8Z4TGic6xW70FKThA5HYEKKyBpOOsucTOD1DjU3fZxDg+K3zBJcXMFnt/4yQiZnf5+MiOMSXQ9PaEK/Ilh1DeA==", "dev": true, "license": "MIT", "dependencies": { @@ -2322,14 +2756,16 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-logical-assignment-operators": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.28.6.tgz", - "integrity": "sha512-+anKKair6gpi8VsM/95kmomGNMD0eLz1NQ8+Pfw5sAwWH9fGYXT50E55ZpV0pHUHWf6IUTWPM+f/7AAff+wr9A==", + "node_modules/@babel/plugin-transform-async-generator-functions": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.28.0.tgz", + "integrity": "sha512-BEOdvX4+M765icNPZeidyADIvQ1m1gmunXufXxvRESy/jNNyfovIqUyE7MVgGBjWktCoJlzvFA1To2O4ymIO3Q==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.28.6" + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-remap-async-to-generator": "^7.27.1", + "@babel/traverse": "^7.28.0" }, "engines": { "node": ">=6.9.0" @@ -2338,14 +2774,16 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-member-expression-literals": { + "node_modules/@babel/plugin-transform-async-to-generator": { "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.27.1.tgz", - "integrity": "sha512-hqoBX4dcZ1I33jCSWcXrP+1Ku7kdqXf1oeah7ooKOIiAdKQ+uqftgCFNOSzA5AMS2XIHEYeGFg4cKRCdpxzVOQ==", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.27.1.tgz", + "integrity": "sha512-NREkZsZVJS4xmTr8qzE5y8AfIPqsdQfRuUiLRTEzb7Qii8iFWCyDKaUV2c0rCuh4ljDZ98ALHP/PetiBV2nddA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-module-imports": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-remap-async-to-generator": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -2354,14 +2792,13 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-modules-amd": { + "node_modules/@babel/plugin-transform-block-scoped-functions": { "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.27.1.tgz", - "integrity": "sha512-iCsytMg/N9/oFq6n+gFTvUYDZQOMK5kEdeYxmxt91fcJGycfxVP9CnrxoliM0oumFERba2i8ZtwRUCMhvP1LnA==", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.27.1.tgz", + "integrity": "sha512-cnqkuOtZLapWYZUYM5rVIdv1nXYuFVIltZ6ZJ7nIj585QsjKM5dhL2Fu/lICXZ1OyIAFc7Qy+bvDAtTXqGrlhg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-module-transforms": "^7.27.1", "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { @@ -2371,14 +2808,13 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-modules-commonjs": { + "node_modules/@babel/plugin-transform-block-scoping": { "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.28.6.tgz", - "integrity": "sha512-jppVbf8IV9iWWwWTQIxJMAJCWBuuKx71475wHwYytrRGQ2CWiDvYlADQno3tcYpS/T2UUWFQp3nVtYfK/YBQrA==", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.28.6.tgz", + "integrity": "sha512-tt/7wOtBmwHPNMPu7ax4pdPz6shjFrmHDghvNC+FG9Qvj7D6mJcoRQIF5dy4njmxR941l6rgtvfSB2zX3VlUIw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-module-transforms": "^7.28.6", "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { @@ -2388,17 +2824,15 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-modules-systemjs": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.29.0.tgz", - "integrity": "sha512-PrujnVFbOdUpw4UHiVwKvKRLMMic8+eC0CuNlxjsyZUiBjhFdPsewdXCkveh2KqBA9/waD0W1b4hXSOBQJezpQ==", + "node_modules/@babel/plugin-transform-class-properties": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.28.6.tgz", + "integrity": "sha512-dY2wS3I2G7D697VHndN91TJr8/AAfXQNt5ynCTI/MpxMsSzHp+52uNivYT5wCPax3whc47DR8Ba7cmlQMg24bw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-module-transforms": "^7.28.6", - "@babel/helper-plugin-utils": "^7.28.6", - "@babel/helper-validator-identifier": "^7.28.5", - "@babel/traverse": "^7.29.0" + "@babel/helper-create-class-features-plugin": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -2407,48 +2841,53 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-modules-umd": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.27.1.tgz", - "integrity": "sha512-iQBE/xC5BV1OxJbp6WG7jq9IWiD+xxlZhLrdwpPkTX3ydmXdvoCpyfJN7acaIBZaOqTfr76pgzqBJflNbeRK+w==", + "node_modules/@babel/plugin-transform-class-static-block": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.28.6.tgz", + "integrity": "sha512-rfQ++ghVwTWTqQ7w8qyDxL1XGihjBss4CmTgGRCTAC9RIbhVpyp4fOeZtta0Lbf+dTNIVJer6ych2ibHwkZqsQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-module-transforms": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-create-class-features-plugin": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" }, "peerDependencies": { - "@babel/core": "^7.0.0-0" + "@babel/core": "^7.12.0" } }, - "node_modules/@babel/plugin-transform-named-capturing-groups-regex": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.29.0.tgz", - "integrity": "sha512-1CZQA5KNAD6ZYQLPw7oi5ewtDNxH/2vuCh+6SmvgDfhumForvs8a1o9n0UrEoBD8HU4djO2yWngTQlXl1NDVEQ==", + "node_modules/@babel/plugin-transform-classes": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.28.6.tgz", + "integrity": "sha512-EF5KONAqC5zAqT783iMGuM2ZtmEBy+mJMOKl2BCvPZ2lVrwvXnB6o+OBWCS+CoeCCpVRF2sA2RBKUxvT8tQT5Q==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.28.5", - "@babel/helper-plugin-utils": "^7.28.6" + "@babel/helper-annotate-as-pure": "^7.27.3", + "@babel/helper-compilation-targets": "^7.28.6", + "@babel/helper-globals": "^7.28.0", + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/helper-replace-supers": "^7.28.6", + "@babel/traverse": "^7.28.6" }, "engines": { "node": ">=6.9.0" }, "peerDependencies": { - "@babel/core": "^7.0.0" + "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-new-target": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.27.1.tgz", - "integrity": "sha512-f6PiYeqXQ05lYq3TIfIDu/MtliKUbNwkGApPUvyo6+tc7uaR4cPjPe7DFPr15Uyycg2lZU6btZ575CuQoYh7MQ==", + "node_modules/@babel/plugin-transform-computed-properties": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.28.6.tgz", + "integrity": "sha512-bcc3k0ijhHbc2lEfpFHgx7eYw9KNXqOerKWfzbxEHUGKnS3sz9C4CNL9OiFN1297bDNfUiSO7DaLzbvHQQQ1BQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/template": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -2457,14 +2896,15 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-nullish-coalescing-operator": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.28.6.tgz", - "integrity": "sha512-3wKbRgmzYbw24mDJXT7N+ADXw8BC/imU9yo9c9X9NKaLF1fW+e5H1U5QjMUBe4Qo4Ox/o++IyUkl1sVCLgevKg==", + "node_modules/@babel/plugin-transform-destructuring": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.28.5.tgz", + "integrity": "sha512-Kl9Bc6D0zTUcFUvkNuQh4eGXPKKNDOJQXVyyM4ZAQPMveniJdxi8XMJwLo+xSoW3MIq81bD33lcUe9kZpl0MCw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.28.6" + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/traverse": "^7.28.5" }, "engines": { "node": ">=6.9.0" @@ -2473,13 +2913,14 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-numeric-separator": { + "node_modules/@babel/plugin-transform-dotall-regex": { "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.28.6.tgz", - "integrity": "sha512-SJR8hPynj8outz+SlStQSwvziMN4+Bq99it4tMIf5/Caq+3iOc0JtKyse8puvyXkk3eFRIA5ID/XfunGgO5i6w==", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.28.6.tgz", + "integrity": "sha512-SljjowuNKB7q5Oayv4FoPzeB74g3QgLt8IVJw9ADvWy3QnUb/01aw8I4AVv8wYnPvQz2GDDZ/g3GhcNyDBI4Bg==", "dev": true, "license": "MIT", "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.28.5", "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { @@ -2489,18 +2930,14 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-object-rest-spread": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.28.6.tgz", - "integrity": "sha512-5rh+JR4JBC4pGkXLAcYdLHZjXudVxWMXbB6u6+E9lRL5TrGVbHt1TjxGbZ8CkmYw9zjkB7jutzOROArsqtncEA==", + "node_modules/@babel/plugin-transform-duplicate-keys": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.27.1.tgz", + "integrity": "sha512-MTyJk98sHvSs+cvZ4nOauwTTG1JeonDjSGvGGUNHreGQns+Mpt6WX/dVzWBHgg+dYZhkC4X+zTDfkTU+Vy9y7Q==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-compilation-targets": "^7.28.6", - "@babel/helper-plugin-utils": "^7.28.6", - "@babel/plugin-transform-destructuring": "^7.28.5", - "@babel/plugin-transform-parameters": "^7.27.7", - "@babel/traverse": "^7.28.6" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -2509,15 +2946,31 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-object-super": { + "node_modules/@babel/plugin-transform-duplicate-named-capturing-groups-regex": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-named-capturing-groups-regex/-/plugin-transform-duplicate-named-capturing-groups-regex-7.29.0.tgz", + "integrity": "sha512-zBPcW2lFGxdiD8PUnPwJjag2J9otbcLQzvbiOzDxpYXyCuYX9agOwMPGn1prVH0a4qzhCKu24rlH4c1f7yA8rw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.28.5", + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-transform-dynamic-import": { "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.27.1.tgz", - "integrity": "sha512-SFy8S9plRPbIcxlJ8A6mT/CxFdJx/c04JEctz4jf8YZaVS2px34j7NXRrlGlHkN/M2gnpL37ZpGRGVFLd3l8Ng==", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.27.1.tgz", + "integrity": "sha512-MHzkWQcEmjzzVW9j2q8LGjwGWpG2mjwaaB0BNQwst3FIjqsg8Ct/mIZlvSPJvfi9y2AC8mi/ktxbFVL9pZ1I4A==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/helper-replace-supers": "^7.27.1" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -2526,14 +2979,15 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-optional-catch-binding": { + "node_modules/@babel/plugin-transform-explicit-resource-management": { "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.28.6.tgz", - "integrity": "sha512-R8ja/Pyrv0OGAvAXQhSTmWyPJPml+0TMqXlO5w+AsMEiwb2fg3WkOvob7UxFSL3OIttFSGSRFKQsOhJ/X6HQdQ==", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-explicit-resource-management/-/plugin-transform-explicit-resource-management-7.28.6.tgz", + "integrity": "sha512-Iao5Konzx2b6g7EPqTy40UZbcdXE126tTxVFr/nAIj+WItNxjKSYTEw3RC+A2/ZetmdJsgueL1KhaMCQHkLPIg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.28.6" + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/plugin-transform-destructuring": "^7.28.5" }, "engines": { "node": ">=6.9.0" @@ -2542,15 +2996,14 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-optional-chaining": { + "node_modules/@babel/plugin-transform-exponentiation-operator": { "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.28.6.tgz", - "integrity": "sha512-A4zobikRGJTsX9uqVFdafzGkqD30t26ck2LmOzAuLL8b2x6k3TIqRiT2xVvA9fNmFeTX484VpsdgmKNA0bS23w==", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.28.6.tgz", + "integrity": "sha512-WitabqiGjV/vJ0aPOLSFfNY1u9U3R7W36B03r5I2KoNix+a3sOhJ3pKFB3R5It9/UiK78NiO0KE9P21cMhlPkw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.28.6", - "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -2559,10 +3012,10 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-parameters": { - "version": "7.27.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.27.7.tgz", - "integrity": "sha512-qBkYTYCb76RRxUM6CcZA5KRu8K4SM8ajzVeUgVdMVO9NN9uI/GaVmBg/WKJJGnNokV9SY8FxNOVWGXzqzUidBg==", + "node_modules/@babel/plugin-transform-export-namespace-from": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.27.1.tgz", + "integrity": "sha512-tQvHWSZ3/jH2xuq/vZDy0jNn+ZdXJeM8gHvX4lnJmsc3+50yPlWdZXIc5ay+umX+2/tJIqHqiEqcJvxlmIvRvQ==", "dev": true, "license": "MIT", "dependencies": { @@ -2575,15 +3028,15 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-private-methods": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.28.6.tgz", - "integrity": "sha512-piiuapX9CRv7+0st8lmuUlRSmX6mBcVeNQ1b4AYzJxfCMuBfB0vBXDiGSmm03pKJw1v6cZ8KSeM+oUnM6yAExg==", + "node_modules/@babel/plugin-transform-for-of": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.27.1.tgz", + "integrity": "sha512-BfbWFFEJFQzLCQ5N8VocnCtA8J1CLkNTe2Ms2wocj75dd6VpiqS5Z5quTYcUoo4Yq+DN0rtikODccuv7RU81sw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.28.6", - "@babel/helper-plugin-utils": "^7.28.6" + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -2592,16 +3045,16 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-private-property-in-object": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.28.6.tgz", - "integrity": "sha512-b97jvNSOb5+ehyQmBpmhOCiUC5oVK4PMnpRvO7+ymFBoqYjeDHIU9jnrNUuwHOiL9RpGDoKBpSViarV+BU+eVA==", + "node_modules/@babel/plugin-transform-function-name": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.27.1.tgz", + "integrity": "sha512-1bQeydJF9Nr1eBCMMbC+hdwmRlsv5XYOMu03YSWFwNs0HsAmtSxxF1fyuYPqemVldVyFmlCU7w8UE14LupUSZQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.27.3", - "@babel/helper-create-class-features-plugin": "^7.28.6", - "@babel/helper-plugin-utils": "^7.28.6" + "@babel/helper-compilation-targets": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/traverse": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -2610,14 +3063,14 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-property-literals": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.27.1.tgz", - "integrity": "sha512-oThy3BCuCha8kDZ8ZkgOg2exvPYUlprMukKQXI1r1pJ47NCvxfkEy8vK+r/hT9nF0Aa4H1WUPZZjHTFtAhGfmQ==", + "node_modules/@babel/plugin-transform-json-strings": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.28.6.tgz", + "integrity": "sha512-Nr+hEN+0geQkzhbdgQVPoqr47lZbm+5fCUmO70722xJZd0Mvb59+33QLImGj6F+DkK3xgDi1YVysP8whD6FQAw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -2626,14 +3079,14 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-regenerator": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.29.0.tgz", - "integrity": "sha512-FijqlqMA7DmRdg/aINBSs04y8XNTYw/lr1gJ2WsmBnnaNw1iS43EPkJW+zK7z65auG3AWRFXWj+NcTQwYptUog==", + "node_modules/@babel/plugin-transform-literals": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.27.1.tgz", + "integrity": "sha512-0HCFSepIpLTkLcsi86GG3mTUzxV5jpmbv97hTETW3yzrAij8aqlD36toB1D0daVFJM8NK6GvKO0gslVQmm+zZA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.28.6" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -2642,27 +3095,26 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-regexp-modifiers": { + "node_modules/@babel/plugin-transform-logical-assignment-operators": { "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regexp-modifiers/-/plugin-transform-regexp-modifiers-7.28.6.tgz", - "integrity": "sha512-QGWAepm9qxpaIs7UM9FvUSnCGlb8Ua1RhyM4/veAxLwt3gMat/LSGrZixyuj4I6+Kn9iwvqCyPTtbdxanYoWYg==", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.28.6.tgz", + "integrity": "sha512-+anKKair6gpi8VsM/95kmomGNMD0eLz1NQ8+Pfw5sAwWH9fGYXT50E55ZpV0pHUHWf6IUTWPM+f/7AAff+wr9A==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.28.5", "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" }, "peerDependencies": { - "@babel/core": "^7.0.0" + "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-reserved-words": { + "node_modules/@babel/plugin-transform-member-expression-literals": { "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.27.1.tgz", - "integrity": "sha512-V2ABPHIJX4kC7HegLkYoDpfg9PVmuWy/i6vUM5eGK22bx4YVFD3M5F0QQnWQoDs6AGsUWTVOopBiMFQgHaSkVw==", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.27.1.tgz", + "integrity": "sha512-hqoBX4dcZ1I33jCSWcXrP+1Ku7kdqXf1oeah7ooKOIiAdKQ+uqftgCFNOSzA5AMS2XIHEYeGFg4cKRCdpxzVOQ==", "dev": true, "license": "MIT", "dependencies": { @@ -2675,19 +3127,15 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-runtime": { - "version": "7.28.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.28.3.tgz", - "integrity": "sha512-Y6ab1kGqZ0u42Zv/4a7l0l72n9DKP/MKoKWaUSBylrhNZO2prYuqFOLbn5aW5SIFXwSH93yfjbgllL8lxuGKLg==", + "node_modules/@babel/plugin-transform-modules-amd": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.27.1.tgz", + "integrity": "sha512-iCsytMg/N9/oFq6n+gFTvUYDZQOMK5kEdeYxmxt91fcJGycfxVP9CnrxoliM0oumFERba2i8ZtwRUCMhvP1LnA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-module-imports": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1", - "babel-plugin-polyfill-corejs2": "^0.4.14", - "babel-plugin-polyfill-corejs3": "^0.13.0", - "babel-plugin-polyfill-regenerator": "^0.6.5", - "semver": "^6.3.1" + "@babel/helper-module-transforms": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -2696,24 +3144,15 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-runtime/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/@babel/plugin-transform-shorthand-properties": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.27.1.tgz", - "integrity": "sha512-N/wH1vcn4oYawbJ13Y/FxcQrWk63jhfNa7jef0ih7PHSIHX2LB7GWE1rkPrOnka9kwMxb6hMl19p7lidA+EHmQ==", + "node_modules/@babel/plugin-transform-modules-commonjs": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.28.6.tgz", + "integrity": "sha512-jppVbf8IV9iWWwWTQIxJMAJCWBuuKx71475wHwYytrRGQ2CWiDvYlADQno3tcYpS/T2UUWFQp3nVtYfK/YBQrA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-module-transforms": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -2722,15 +3161,17 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-spread": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.28.6.tgz", - "integrity": "sha512-9U4QObUC0FtJl05AsUcodau/RWDytrU6uKgkxu09mLR9HLDAtUMoPuuskm5huQsoktmsYpI+bGmq+iapDcriKA==", + "node_modules/@babel/plugin-transform-modules-systemjs": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.29.0.tgz", + "integrity": "sha512-PrujnVFbOdUpw4UHiVwKvKRLMMic8+eC0CuNlxjsyZUiBjhFdPsewdXCkveh2KqBA9/waD0W1b4hXSOBQJezpQ==", "dev": true, "license": "MIT", "dependencies": { + "@babel/helper-module-transforms": "^7.28.6", "@babel/helper-plugin-utils": "^7.28.6", - "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" + "@babel/helper-validator-identifier": "^7.28.5", + "@babel/traverse": "^7.29.0" }, "engines": { "node": ">=6.9.0" @@ -2739,13 +3180,14 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-sticky-regex": { + "node_modules/@babel/plugin-transform-modules-umd": { "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.27.1.tgz", - "integrity": "sha512-lhInBO5bi/Kowe2/aLdBAawijx+q1pQzicSgnkB6dUPc1+RC8QmJHKf2OjvU+NZWitguJHEaEmbV6VWEouT58g==", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.27.1.tgz", + "integrity": "sha512-iQBE/xC5BV1OxJbp6WG7jq9IWiD+xxlZhLrdwpPkTX3ydmXdvoCpyfJN7acaIBZaOqTfr76pgzqBJflNbeRK+w==", "dev": true, "license": "MIT", "dependencies": { + "@babel/helper-module-transforms": "^7.27.1", "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { @@ -2755,26 +3197,27 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-template-literals": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.27.1.tgz", - "integrity": "sha512-fBJKiV7F2DxZUkg5EtHKXQdbsbURW3DZKQUWphDum0uRP6eHGGa/He9mc0mypL680pb+e/lDIthRohlv8NCHkg==", + "node_modules/@babel/plugin-transform-named-capturing-groups-regex": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.29.0.tgz", + "integrity": "sha512-1CZQA5KNAD6ZYQLPw7oi5ewtDNxH/2vuCh+6SmvgDfhumForvs8a1o9n0UrEoBD8HU4djO2yWngTQlXl1NDVEQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-create-regexp-features-plugin": "^7.28.5", + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" }, "peerDependencies": { - "@babel/core": "^7.0.0-0" + "@babel/core": "^7.0.0" } }, - "node_modules/@babel/plugin-transform-typeof-symbol": { + "node_modules/@babel/plugin-transform-new-target": { "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.27.1.tgz", - "integrity": "sha512-RiSILC+nRJM7FY5srIyc4/fGIwUhyDuuBSdWn4y6yT6gm652DpCHZjIipgn6B7MQ1ITOUnAKWixEUjQRIBIcLw==", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.27.1.tgz", + "integrity": "sha512-f6PiYeqXQ05lYq3TIfIDu/MtliKUbNwkGApPUvyo6+tc7uaR4cPjPe7DFPr15Uyycg2lZU6btZ575CuQoYh7MQ==", "dev": true, "license": "MIT", "dependencies": { @@ -2787,14 +3230,14 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-unicode-escapes": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.27.1.tgz", - "integrity": "sha512-Ysg4v6AmF26k9vpfFuTZg8HRfVWzsh1kVfowA23y9j/Gu6dOuahdUVhkLqpObp3JIv27MLSii6noRnuKN8H0Mg==", + "node_modules/@babel/plugin-transform-nullish-coalescing-operator": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.28.6.tgz", + "integrity": "sha512-3wKbRgmzYbw24mDJXT7N+ADXw8BC/imU9yo9c9X9NKaLF1fW+e5H1U5QjMUBe4Qo4Ox/o++IyUkl1sVCLgevKg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -2803,14 +3246,13 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-unicode-property-regex": { + "node_modules/@babel/plugin-transform-numeric-separator": { "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.28.6.tgz", - "integrity": "sha512-4Wlbdl/sIZjzi/8St0evF0gEZrgOswVO6aOzqxh1kDZOl9WmLrHq2HtGhnOJZmHZYKP8WZ1MDLCt5DAWwRo57A==", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.28.6.tgz", + "integrity": "sha512-SJR8hPynj8outz+SlStQSwvziMN4+Bq99it4tMIf5/Caq+3iOc0JtKyse8puvyXkk3eFRIA5ID/XfunGgO5i6w==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.28.5", "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { @@ -2820,15 +3262,18 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-unicode-regex": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.27.1.tgz", - "integrity": "sha512-xvINq24TRojDuyt6JGtHmkVkrfVV3FPT16uytxImLeBZqW3/H52yN+kM1MGuyPkIQxrzKwPHs5U/MP3qKyzkGw==", + "node_modules/@babel/plugin-transform-object-rest-spread": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.28.6.tgz", + "integrity": "sha512-5rh+JR4JBC4pGkXLAcYdLHZjXudVxWMXbB6u6+E9lRL5TrGVbHt1TjxGbZ8CkmYw9zjkB7jutzOROArsqtncEA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-compilation-targets": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/plugin-transform-destructuring": "^7.28.5", + "@babel/plugin-transform-parameters": "^7.27.7", + "@babel/traverse": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -2837,100 +3282,31 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-unicode-sets-regex": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.28.6.tgz", - "integrity": "sha512-/wHc/paTUmsDYN7SZkpWxogTOBNnlx7nBQYfy6JJlCT7G3mVhltk3e++N7zV0XfgGsrqBxd4rJQt9H16I21Y1Q==", + "node_modules/@babel/plugin-transform-object-super": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.27.1.tgz", + "integrity": "sha512-SFy8S9plRPbIcxlJ8A6mT/CxFdJx/c04JEctz4jf8YZaVS2px34j7NXRrlGlHkN/M2gnpL37ZpGRGVFLd3l8Ng==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.28.5", - "@babel/helper-plugin-utils": "^7.28.6" + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-replace-supers": "^7.27.1" }, "engines": { "node": ">=6.9.0" }, "peerDependencies": { - "@babel/core": "^7.0.0" + "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/preset-env": { - "version": "7.28.3", - "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.28.3.tgz", - "integrity": "sha512-ROiDcM+GbYVPYBOeCR6uBXKkQpBExLl8k9HO1ygXEyds39j+vCCsjmj7S8GOniZQlEs81QlkdJZe76IpLSiqpg==", + "node_modules/@babel/plugin-transform-optional-catch-binding": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.28.6.tgz", + "integrity": "sha512-R8ja/Pyrv0OGAvAXQhSTmWyPJPml+0TMqXlO5w+AsMEiwb2fg3WkOvob7UxFSL3OIttFSGSRFKQsOhJ/X6HQdQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/compat-data": "^7.28.0", - "@babel/helper-compilation-targets": "^7.27.2", - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/helper-validator-option": "^7.27.1", - "@babel/plugin-bugfix-firefox-class-in-computed-class-key": "^7.27.1", - "@babel/plugin-bugfix-safari-class-field-initializer-scope": "^7.27.1", - "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.27.1", - "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.27.1", - "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.28.3", - "@babel/plugin-proposal-private-property-in-object": "7.21.0-placeholder-for-preset-env.2", - "@babel/plugin-syntax-import-assertions": "^7.27.1", - "@babel/plugin-syntax-import-attributes": "^7.27.1", - "@babel/plugin-syntax-unicode-sets-regex": "^7.18.6", - "@babel/plugin-transform-arrow-functions": "^7.27.1", - "@babel/plugin-transform-async-generator-functions": "^7.28.0", - "@babel/plugin-transform-async-to-generator": "^7.27.1", - "@babel/plugin-transform-block-scoped-functions": "^7.27.1", - "@babel/plugin-transform-block-scoping": "^7.28.0", - "@babel/plugin-transform-class-properties": "^7.27.1", - "@babel/plugin-transform-class-static-block": "^7.28.3", - "@babel/plugin-transform-classes": "^7.28.3", - "@babel/plugin-transform-computed-properties": "^7.27.1", - "@babel/plugin-transform-destructuring": "^7.28.0", - "@babel/plugin-transform-dotall-regex": "^7.27.1", - "@babel/plugin-transform-duplicate-keys": "^7.27.1", - "@babel/plugin-transform-duplicate-named-capturing-groups-regex": "^7.27.1", - "@babel/plugin-transform-dynamic-import": "^7.27.1", - "@babel/plugin-transform-explicit-resource-management": "^7.28.0", - "@babel/plugin-transform-exponentiation-operator": "^7.27.1", - "@babel/plugin-transform-export-namespace-from": "^7.27.1", - "@babel/plugin-transform-for-of": "^7.27.1", - "@babel/plugin-transform-function-name": "^7.27.1", - "@babel/plugin-transform-json-strings": "^7.27.1", - "@babel/plugin-transform-literals": "^7.27.1", - "@babel/plugin-transform-logical-assignment-operators": "^7.27.1", - "@babel/plugin-transform-member-expression-literals": "^7.27.1", - "@babel/plugin-transform-modules-amd": "^7.27.1", - "@babel/plugin-transform-modules-commonjs": "^7.27.1", - "@babel/plugin-transform-modules-systemjs": "^7.27.1", - "@babel/plugin-transform-modules-umd": "^7.27.1", - "@babel/plugin-transform-named-capturing-groups-regex": "^7.27.1", - "@babel/plugin-transform-new-target": "^7.27.1", - "@babel/plugin-transform-nullish-coalescing-operator": "^7.27.1", - "@babel/plugin-transform-numeric-separator": "^7.27.1", - "@babel/plugin-transform-object-rest-spread": "^7.28.0", - "@babel/plugin-transform-object-super": "^7.27.1", - "@babel/plugin-transform-optional-catch-binding": "^7.27.1", - "@babel/plugin-transform-optional-chaining": "^7.27.1", - "@babel/plugin-transform-parameters": "^7.27.7", - "@babel/plugin-transform-private-methods": "^7.27.1", - "@babel/plugin-transform-private-property-in-object": "^7.27.1", - "@babel/plugin-transform-property-literals": "^7.27.1", - "@babel/plugin-transform-regenerator": "^7.28.3", - "@babel/plugin-transform-regexp-modifiers": "^7.27.1", - "@babel/plugin-transform-reserved-words": "^7.27.1", - "@babel/plugin-transform-shorthand-properties": "^7.27.1", - "@babel/plugin-transform-spread": "^7.27.1", - "@babel/plugin-transform-sticky-regex": "^7.27.1", - "@babel/plugin-transform-template-literals": "^7.27.1", - "@babel/plugin-transform-typeof-symbol": "^7.27.1", - "@babel/plugin-transform-unicode-escapes": "^7.27.1", - "@babel/plugin-transform-unicode-property-regex": "^7.27.1", - "@babel/plugin-transform-unicode-regex": "^7.27.1", - "@babel/plugin-transform-unicode-sets-regex": "^7.27.1", - "@babel/preset-modules": "0.1.6-no-external-plugins", - "babel-plugin-polyfill-corejs2": "^0.4.14", - "babel-plugin-polyfill-corejs3": "^0.13.0", - "babel-plugin-polyfill-regenerator": "^0.6.5", - "core-js-compat": "^3.43.0", - "semver": "^6.3.1" + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -2939,165 +3315,572 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/preset-env/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/@babel/preset-modules": { - "version": "0.1.6-no-external-plugins", - "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.6-no-external-plugins.tgz", - "integrity": "sha512-HrcgcIESLm9aIR842yhJ5RWan/gebQUJ6E/E5+rf0y9o6oj7w0Br+sWuL6kEQ/o/AdfvR1Je9jG18/gnpwjEyA==", + "node_modules/@babel/plugin-transform-optional-chaining": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.28.6.tgz", + "integrity": "sha512-A4zobikRGJTsX9uqVFdafzGkqD30t26ck2LmOzAuLL8b2x6k3TIqRiT2xVvA9fNmFeTX484VpsdgmKNA0bS23w==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.0.0", - "@babel/types": "^7.4.4", - "esutils": "^2.0.2" + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" }, "peerDependencies": { - "@babel/core": "^7.0.0-0 || ^8.0.0-0 <8.0.0" + "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/runtime": { - "version": "7.28.3", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.3.tgz", - "integrity": "sha512-9uIQ10o0WGdpP6GDhXcdOJPJuDgFtIDtN/9+ArJQ2NAfAmiuhTQdzkaTGR33v43GYS2UrSA0eX2pPPHoFVvpxA==", + "node_modules/@babel/plugin-transform-parameters": { + "version": "7.27.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.27.7.tgz", + "integrity": "sha512-qBkYTYCb76RRxUM6CcZA5KRu8K4SM8ajzVeUgVdMVO9NN9uI/GaVmBg/WKJJGnNokV9SY8FxNOVWGXzqzUidBg==", + "dev": true, "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, "engines": { "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/template": { + "node_modules/@babel/plugin-transform-private-methods": { "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz", - "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.28.6.tgz", + "integrity": "sha512-piiuapX9CRv7+0st8lmuUlRSmX6mBcVeNQ1b4AYzJxfCMuBfB0vBXDiGSmm03pKJw1v6cZ8KSeM+oUnM6yAExg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.28.6", - "@babel/parser": "^7.28.6", - "@babel/types": "^7.28.6" + "@babel/helper-create-class-features-plugin": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/traverse": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz", - "integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==", + "node_modules/@babel/plugin-transform-private-property-in-object": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.28.6.tgz", + "integrity": "sha512-b97jvNSOb5+ehyQmBpmhOCiUC5oVK4PMnpRvO7+ymFBoqYjeDHIU9jnrNUuwHOiL9RpGDoKBpSViarV+BU+eVA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.29.0", - "@babel/generator": "^7.29.0", - "@babel/helper-globals": "^7.28.0", - "@babel/parser": "^7.29.0", - "@babel/template": "^7.28.6", - "@babel/types": "^7.29.0", - "debug": "^4.3.1" + "@babel/helper-annotate-as-pure": "^7.27.3", + "@babel/helper-create-class-features-plugin": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/traverse/node_modules/@babel/generator": { - "version": "7.29.1", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.1.tgz", - "integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==", + "node_modules/@babel/plugin-transform-property-literals": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.27.1.tgz", + "integrity": "sha512-oThy3BCuCha8kDZ8ZkgOg2exvPYUlprMukKQXI1r1pJ47NCvxfkEy8vK+r/hT9nF0Aa4H1WUPZZjHTFtAhGfmQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/parser": "^7.29.0", - "@babel/types": "^7.29.0", - "@jridgewell/gen-mapping": "^0.3.12", - "@jridgewell/trace-mapping": "^0.3.28", - "jsesc": "^3.0.2" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/types": { + "node_modules/@babel/plugin-transform-regenerator": { "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", - "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.29.0.tgz", + "integrity": "sha512-FijqlqMA7DmRdg/aINBSs04y8XNTYw/lr1gJ2WsmBnnaNw1iS43EPkJW+zK7z65auG3AWRFXWj+NcTQwYptUog==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-string-parser": "^7.27.1", - "@babel/helper-validator-identifier": "^7.28.5" + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@cspotcode/source-map-consumer": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/@cspotcode/source-map-consumer/-/source-map-consumer-0.8.0.tgz", - "integrity": "sha512-41qniHzTU8yAGbCp04ohlmSrZf8bkf/iJsl3V0dRGsQN/5GFfx+LbCSsCpp2gqrqjTVg/K6O8ycoV35JIwAzAg==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">= 12" - } - }, - "node_modules/@cspotcode/source-map-support": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.7.0.tgz", - "integrity": "sha512-X4xqRHqN8ACt2aHVe51OxeA2HjbcL4MqFqXkrmQszJ1NOUuUu5u6Vqx/0lZSVNku7velL5FC/s5uEAj1lsBMhA==", + "node_modules/@babel/plugin-transform-regexp-modifiers": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regexp-modifiers/-/plugin-transform-regexp-modifiers-7.28.6.tgz", + "integrity": "sha512-QGWAepm9qxpaIs7UM9FvUSnCGlb8Ua1RhyM4/veAxLwt3gMat/LSGrZixyuj4I6+Kn9iwvqCyPTtbdxanYoWYg==", "dev": true, "license": "MIT", "dependencies": { - "@cspotcode/source-map-consumer": "0.8.0" + "@babel/helper-create-regexp-features-plugin": "^7.28.5", + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { - "node": ">=12" + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" } }, - "node_modules/@discoveryjs/json-ext": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.6.3.tgz", - "integrity": "sha512-4B4OijXeVNOPZlYA2oEwWOTkzyltLao+xbotHQeqN++Rv27Y6s818+n2Qkp8q+Fxhn0t/5lA5X1Mxktud8eayQ==", + "node_modules/@babel/plugin-transform-reserved-words": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.27.1.tgz", + "integrity": "sha512-V2ABPHIJX4kC7HegLkYoDpfg9PVmuWy/i6vUM5eGK22bx4YVFD3M5F0QQnWQoDs6AGsUWTVOopBiMFQgHaSkVw==", "dev": true, "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, "engines": { - "node": ">=14.17.0" + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@es-joy/jsdoccomment": { - "version": "0.84.0", - "resolved": "https://registry.npmjs.org/@es-joy/jsdoccomment/-/jsdoccomment-0.84.0.tgz", - "integrity": "sha512-0xew1CxOam0gV5OMjh2KjFQZsKL2bByX1+q4j3E73MpYIdyUxcZb/xQct9ccUb+ve5KGUYbCUxyPnYB7RbuP+w==", + "node_modules/@babel/plugin-transform-runtime": { + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.28.3.tgz", + "integrity": "sha512-Y6ab1kGqZ0u42Zv/4a7l0l72n9DKP/MKoKWaUSBylrhNZO2prYuqFOLbn5aW5SIFXwSH93yfjbgllL8lxuGKLg==", "dev": true, "license": "MIT", "dependencies": { - "@types/estree": "^1.0.8", - "@typescript-eslint/types": "^8.54.0", - "comment-parser": "1.4.5", - "esquery": "^1.7.0", - "jsdoc-type-pratt-parser": "~7.1.1" + "@babel/helper-module-imports": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1", + "babel-plugin-polyfill-corejs2": "^0.4.14", + "babel-plugin-polyfill-corejs3": "^0.13.0", + "babel-plugin-polyfill-regenerator": "^0.6.5", + "semver": "^6.3.1" }, "engines": { - "node": "^20.19.0 || ^22.13.0 || >=24" + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@es-joy/jsdoccomment/node_modules/@types/estree": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", - "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "node_modules/@babel/plugin-transform-runtime/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true, - "license": "MIT" + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } }, - "node_modules/@es-joy/jsdoccomment/node_modules/@typescript-eslint/types": { - "version": "8.55.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.55.0.tgz", + "node_modules/@babel/plugin-transform-shorthand-properties": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.27.1.tgz", + "integrity": "sha512-N/wH1vcn4oYawbJ13Y/FxcQrWk63jhfNa7jef0ih7PHSIHX2LB7GWE1rkPrOnka9kwMxb6hMl19p7lidA+EHmQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-spread": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.28.6.tgz", + "integrity": "sha512-9U4QObUC0FtJl05AsUcodau/RWDytrU6uKgkxu09mLR9HLDAtUMoPuuskm5huQsoktmsYpI+bGmq+iapDcriKA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-sticky-regex": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.27.1.tgz", + "integrity": "sha512-lhInBO5bi/Kowe2/aLdBAawijx+q1pQzicSgnkB6dUPc1+RC8QmJHKf2OjvU+NZWitguJHEaEmbV6VWEouT58g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-template-literals": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.27.1.tgz", + "integrity": "sha512-fBJKiV7F2DxZUkg5EtHKXQdbsbURW3DZKQUWphDum0uRP6eHGGa/He9mc0mypL680pb+e/lDIthRohlv8NCHkg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-typeof-symbol": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.27.1.tgz", + "integrity": "sha512-RiSILC+nRJM7FY5srIyc4/fGIwUhyDuuBSdWn4y6yT6gm652DpCHZjIipgn6B7MQ1ITOUnAKWixEUjQRIBIcLw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-escapes": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.27.1.tgz", + "integrity": "sha512-Ysg4v6AmF26k9vpfFuTZg8HRfVWzsh1kVfowA23y9j/Gu6dOuahdUVhkLqpObp3JIv27MLSii6noRnuKN8H0Mg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-property-regex": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.28.6.tgz", + "integrity": "sha512-4Wlbdl/sIZjzi/8St0evF0gEZrgOswVO6aOzqxh1kDZOl9WmLrHq2HtGhnOJZmHZYKP8WZ1MDLCt5DAWwRo57A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.28.5", + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-regex": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.27.1.tgz", + "integrity": "sha512-xvINq24TRojDuyt6JGtHmkVkrfVV3FPT16uytxImLeBZqW3/H52yN+kM1MGuyPkIQxrzKwPHs5U/MP3qKyzkGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-sets-regex": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.28.6.tgz", + "integrity": "sha512-/wHc/paTUmsDYN7SZkpWxogTOBNnlx7nBQYfy6JJlCT7G3mVhltk3e++N7zV0XfgGsrqBxd4rJQt9H16I21Y1Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.28.5", + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/preset-env": { + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.28.3.tgz", + "integrity": "sha512-ROiDcM+GbYVPYBOeCR6uBXKkQpBExLl8k9HO1ygXEyds39j+vCCsjmj7S8GOniZQlEs81QlkdJZe76IpLSiqpg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.28.0", + "@babel/helper-compilation-targets": "^7.27.2", + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-validator-option": "^7.27.1", + "@babel/plugin-bugfix-firefox-class-in-computed-class-key": "^7.27.1", + "@babel/plugin-bugfix-safari-class-field-initializer-scope": "^7.27.1", + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.27.1", + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.27.1", + "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.28.3", + "@babel/plugin-proposal-private-property-in-object": "7.21.0-placeholder-for-preset-env.2", + "@babel/plugin-syntax-import-assertions": "^7.27.1", + "@babel/plugin-syntax-import-attributes": "^7.27.1", + "@babel/plugin-syntax-unicode-sets-regex": "^7.18.6", + "@babel/plugin-transform-arrow-functions": "^7.27.1", + "@babel/plugin-transform-async-generator-functions": "^7.28.0", + "@babel/plugin-transform-async-to-generator": "^7.27.1", + "@babel/plugin-transform-block-scoped-functions": "^7.27.1", + "@babel/plugin-transform-block-scoping": "^7.28.0", + "@babel/plugin-transform-class-properties": "^7.27.1", + "@babel/plugin-transform-class-static-block": "^7.28.3", + "@babel/plugin-transform-classes": "^7.28.3", + "@babel/plugin-transform-computed-properties": "^7.27.1", + "@babel/plugin-transform-destructuring": "^7.28.0", + "@babel/plugin-transform-dotall-regex": "^7.27.1", + "@babel/plugin-transform-duplicate-keys": "^7.27.1", + "@babel/plugin-transform-duplicate-named-capturing-groups-regex": "^7.27.1", + "@babel/plugin-transform-dynamic-import": "^7.27.1", + "@babel/plugin-transform-explicit-resource-management": "^7.28.0", + "@babel/plugin-transform-exponentiation-operator": "^7.27.1", + "@babel/plugin-transform-export-namespace-from": "^7.27.1", + "@babel/plugin-transform-for-of": "^7.27.1", + "@babel/plugin-transform-function-name": "^7.27.1", + "@babel/plugin-transform-json-strings": "^7.27.1", + "@babel/plugin-transform-literals": "^7.27.1", + "@babel/plugin-transform-logical-assignment-operators": "^7.27.1", + "@babel/plugin-transform-member-expression-literals": "^7.27.1", + "@babel/plugin-transform-modules-amd": "^7.27.1", + "@babel/plugin-transform-modules-commonjs": "^7.27.1", + "@babel/plugin-transform-modules-systemjs": "^7.27.1", + "@babel/plugin-transform-modules-umd": "^7.27.1", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.27.1", + "@babel/plugin-transform-new-target": "^7.27.1", + "@babel/plugin-transform-nullish-coalescing-operator": "^7.27.1", + "@babel/plugin-transform-numeric-separator": "^7.27.1", + "@babel/plugin-transform-object-rest-spread": "^7.28.0", + "@babel/plugin-transform-object-super": "^7.27.1", + "@babel/plugin-transform-optional-catch-binding": "^7.27.1", + "@babel/plugin-transform-optional-chaining": "^7.27.1", + "@babel/plugin-transform-parameters": "^7.27.7", + "@babel/plugin-transform-private-methods": "^7.27.1", + "@babel/plugin-transform-private-property-in-object": "^7.27.1", + "@babel/plugin-transform-property-literals": "^7.27.1", + "@babel/plugin-transform-regenerator": "^7.28.3", + "@babel/plugin-transform-regexp-modifiers": "^7.27.1", + "@babel/plugin-transform-reserved-words": "^7.27.1", + "@babel/plugin-transform-shorthand-properties": "^7.27.1", + "@babel/plugin-transform-spread": "^7.27.1", + "@babel/plugin-transform-sticky-regex": "^7.27.1", + "@babel/plugin-transform-template-literals": "^7.27.1", + "@babel/plugin-transform-typeof-symbol": "^7.27.1", + "@babel/plugin-transform-unicode-escapes": "^7.27.1", + "@babel/plugin-transform-unicode-property-regex": "^7.27.1", + "@babel/plugin-transform-unicode-regex": "^7.27.1", + "@babel/plugin-transform-unicode-sets-regex": "^7.27.1", + "@babel/preset-modules": "0.1.6-no-external-plugins", + "babel-plugin-polyfill-corejs2": "^0.4.14", + "babel-plugin-polyfill-corejs3": "^0.13.0", + "babel-plugin-polyfill-regenerator": "^0.6.5", + "core-js-compat": "^3.43.0", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/preset-env/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/preset-modules": { + "version": "0.1.6-no-external-plugins", + "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.6-no-external-plugins.tgz", + "integrity": "sha512-HrcgcIESLm9aIR842yhJ5RWan/gebQUJ6E/E5+rf0y9o6oj7w0Br+sWuL6kEQ/o/AdfvR1Je9jG18/gnpwjEyA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/types": "^7.4.4", + "esutils": "^2.0.2" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/@babel/runtime": { + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.3.tgz", + "integrity": "sha512-9uIQ10o0WGdpP6GDhXcdOJPJuDgFtIDtN/9+ArJQ2NAfAmiuhTQdzkaTGR33v43GYS2UrSA0eX2pPPHoFVvpxA==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/template": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz", + "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.28.6", + "@babel/parser": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz", + "integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", + "@babel/helper-globals": "^7.28.0", + "@babel/parser": "^7.29.0", + "@babel/template": "^7.28.6", + "@babel/types": "^7.29.0", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse/node_modules/@babel/generator": { + "version": "7.29.1", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.1.tgz", + "integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.0", + "@babel/types": "^7.29.0", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", + "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@colors/colors": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", + "integrity": "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.1.90" + } + }, + "node_modules/@cspotcode/source-map-consumer": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-consumer/-/source-map-consumer-0.8.0.tgz", + "integrity": "sha512-41qniHzTU8yAGbCp04ohlmSrZf8bkf/iJsl3V0dRGsQN/5GFfx+LbCSsCpp2gqrqjTVg/K6O8ycoV35JIwAzAg==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">= 12" + } + }, + "node_modules/@cspotcode/source-map-support": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.7.0.tgz", + "integrity": "sha512-X4xqRHqN8ACt2aHVe51OxeA2HjbcL4MqFqXkrmQszJ1NOUuUu5u6Vqx/0lZSVNku7velL5FC/s5uEAj1lsBMhA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@cspotcode/source-map-consumer": "0.8.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@discoveryjs/json-ext": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.6.3.tgz", + "integrity": "sha512-4B4OijXeVNOPZlYA2oEwWOTkzyltLao+xbotHQeqN++Rv27Y6s818+n2Qkp8q+Fxhn0t/5lA5X1Mxktud8eayQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.17.0" + } + }, + "node_modules/@es-joy/jsdoccomment": { + "version": "0.84.0", + "resolved": "https://registry.npmjs.org/@es-joy/jsdoccomment/-/jsdoccomment-0.84.0.tgz", + "integrity": "sha512-0xew1CxOam0gV5OMjh2KjFQZsKL2bByX1+q4j3E73MpYIdyUxcZb/xQct9ccUb+ve5KGUYbCUxyPnYB7RbuP+w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.8", + "@typescript-eslint/types": "^8.54.0", + "comment-parser": "1.4.5", + "esquery": "^1.7.0", + "jsdoc-type-pratt-parser": "~7.1.1" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@es-joy/jsdoccomment/node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@es-joy/jsdoccomment/node_modules/@typescript-eslint/types": { + "version": "8.55.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.55.0.tgz", "integrity": "sha512-ujT0Je8GI5BJWi+/mMoR0wxwVEQaxM+pi30xuMiJETlX80OPovb2p9E8ss87gnSVtYXtJoU9U1Cowcr6w2FE0w==", "dev": true, "license": "MIT", @@ -3615,9 +4398,9 @@ } }, "node_modules/@eslint/eslintrc/node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz", + "integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==", "dev": true, "license": "MIT", "dependencies": { @@ -3632,9 +4415,9 @@ } }, "node_modules/@eslint/eslintrc/node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz", + "integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==", "dev": true, "license": "MIT", "dependencies": { @@ -3660,9 +4443,9 @@ "license": "MIT" }, "node_modules/@eslint/eslintrc/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", "dev": true, "license": "ISC", "dependencies": { @@ -3682,6 +4465,29 @@ "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, + "node_modules/@gar/promise-retry": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@gar/promise-retry/-/promise-retry-1.0.3.tgz", + "integrity": "sha512-GmzA9ckNokPypTg10pgpeHNQe7ph+iIKKmhKu3Ob9ANkswreCx7R3cKmY781K8QK3AqVL3xVh9A42JvIAbkkSA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@hono/node-server": { + "version": "1.19.14", + "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.14.tgz", + "integrity": "sha512-GwtvgtXxnWsucXvbQXkRgqksiH2Qed37H9xHZocE5sA3N8O8O8/8FA3uclQXxXVzc9XBZuEOMK7+r02FmSpHtw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.14.1" + }, + "peerDependencies": { + "hono": "^4" + } + }, "node_modules/@humanwhocodes/config-array": { "version": "0.13.0", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz", @@ -3699,9 +4505,9 @@ } }, "node_modules/@humanwhocodes/config-array/node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz", + "integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==", "dev": true, "license": "MIT", "dependencies": { @@ -3710,9 +4516,9 @@ } }, "node_modules/@humanwhocodes/config-array/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", "dev": true, "license": "ISC", "dependencies": { @@ -4067,157 +4873,31 @@ "engines": { "node": ">=18" }, - "peerDependencies": { - "@types/node": ">=18" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - } - } - }, - "node_modules/@inquirer/type": { - "version": "3.0.10", - "resolved": "https://registry.npmjs.org/@inquirer/type/-/type-3.0.10.tgz", - "integrity": "sha512-BvziSRxfz5Ov8ch0z/n3oijRSEcEsHnhggm4xFZe93DHcUCTlutlq9Ox4SVENAfcRD22UQq7T/atg9Wr3k09eA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@types/node": ">=18" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - } - } - }, - "node_modules/@isaacs/balanced-match": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@isaacs/balanced-match/-/balanced-match-4.0.1.tgz", - "integrity": "sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": "20 || >=22" - } - }, - "node_modules/@isaacs/brace-expansion": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/@isaacs/brace-expansion/-/brace-expansion-5.0.1.tgz", - "integrity": "sha512-WMz71T1JS624nWj2n2fnYAuPovhv7EUhk69R6i9dsVyzxt5eM3bjwvgk9L+APE1TRscGysAVMANkB0jh0LQZrQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@isaacs/balanced-match": "^4.0.1" - }, - "engines": { - "node": "20 || >=22" - } - }, - "node_modules/@isaacs/cliui": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", - "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", - "dev": true, - "license": "ISC", - "dependencies": { - "string-width": "^5.1.2", - "string-width-cjs": "npm:string-width@^4.2.0", - "strip-ansi": "^7.0.1", - "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", - "wrap-ansi": "^8.1.0", - "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/@isaacs/cliui/node_modules/ansi-regex": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", - "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } - }, - "node_modules/@isaacs/cliui/node_modules/ansi-styles": { - "version": "6.2.3", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", - "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/@isaacs/cliui/node_modules/emoji-regex": { - "version": "9.2.2", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", - "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", - "dev": true, - "license": "MIT" - }, - "node_modules/@isaacs/cliui/node_modules/string-width": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", - "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", - "dev": true, - "license": "MIT", - "dependencies": { - "eastasianwidth": "^0.2.0", - "emoji-regex": "^9.2.2", - "strip-ansi": "^7.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@isaacs/cliui/node_modules/strip-ansi": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", - "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^6.0.1" - }, - "engines": { - "node": ">=12" + "peerDependencies": { + "@types/node": ">=18" }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } } }, - "node_modules/@isaacs/cliui/node_modules/wrap-ansi": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", - "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "node_modules/@inquirer/type": { + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/@inquirer/type/-/type-3.0.10.tgz", + "integrity": "sha512-BvziSRxfz5Ov8ch0z/n3oijRSEcEsHnhggm4xFZe93DHcUCTlutlq9Ox4SVENAfcRD22UQq7T/atg9Wr3k09eA==", "dev": true, "license": "MIT", - "dependencies": { - "ansi-styles": "^6.1.0", - "string-width": "^5.0.1", - "strip-ansi": "^7.0.1" - }, "engines": { - "node": ">=12" + "node": ">=18" }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } } }, "node_modules/@isaacs/fs-minipass": { @@ -4234,9 +4914,9 @@ } }, "node_modules/@istanbuljs/schema": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", - "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.6.tgz", + "integrity": "sha512-+Sg6GCR/wy1oSmQDFq4LQDAhm3ETKnorxN+y5nbLULOR3P0c14f2Wurzj3/xqPXtasLFfHd5iRFQ7AJt4KH2cw==", "dev": true, "license": "MIT", "engines": { @@ -4846,53 +5526,46 @@ ] }, "node_modules/@modelcontextprotocol/sdk": { - "version": "1.17.3", - "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.17.3.tgz", - "integrity": "sha512-JPwUKWSsbzx+DLFznf/QZ32Qa+ptfbUlHhRLrBQBAFu9iI1iYvizM4p+zhhRDceSsPutXp4z+R/HPVphlIiclg==", + "version": "1.26.0", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.26.0.tgz", + "integrity": "sha512-Y5RmPncpiDtTXDbLKswIJzTqu2hyBKxTNsgKqKclDbhIgg1wgtf1fRuvxgTnRfcnxtvvgbIEcqUOzZrJ6iSReg==", "dev": true, "license": "MIT", "dependencies": { - "ajv": "^6.12.6", + "@hono/node-server": "^1.19.9", + "ajv": "^8.17.1", + "ajv-formats": "^3.0.1", "content-type": "^1.0.5", "cors": "^2.8.5", "cross-spawn": "^7.0.5", "eventsource": "^3.0.2", "eventsource-parser": "^3.0.0", - "express": "^5.0.1", - "express-rate-limit": "^7.5.0", + "express": "^5.2.1", + "express-rate-limit": "^8.2.1", + "hono": "^4.11.4", + "jose": "^6.1.3", + "json-schema-typed": "^8.0.2", "pkce-challenge": "^5.0.0", "raw-body": "^3.0.0", - "zod": "^3.23.8", - "zod-to-json-schema": "^3.24.1" + "zod": "^3.25 || ^4.0", + "zod-to-json-schema": "^3.25.1" }, "engines": { "node": ">=18" - } - }, - "node_modules/@modelcontextprotocol/sdk/node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" + "peerDependencies": { + "@cfworker/json-schema": "^4.1.1", + "zod": "^3.25 || ^4.0" + }, + "peerDependenciesMeta": { + "@cfworker/json-schema": { + "optional": true + }, + "zod": { + "optional": false + } } }, - "node_modules/@modelcontextprotocol/sdk/node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true, - "license": "MIT" - }, "node_modules/@msgpackr-extract/msgpackr-extract-darwin-arm64": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-darwin-arm64/-/msgpackr-extract-darwin-arm64-3.0.3.tgz", @@ -5301,9 +5974,9 @@ } }, "node_modules/@ngtools/webpack": { - "version": "20.3.12", - "resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-20.3.12.tgz", - "integrity": "sha512-ePuofHOtbgvEq2t+hcmL30s4q9HQ/nv9ABwpLiELdVIObcWUnrnizAvM7hujve/9CQL6gRCeEkxPLPS4ZrK9AQ==", + "version": "20.3.25", + "resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-20.3.25.tgz", + "integrity": "sha512-p/YopAgukaIvezv3hsJzJevnNUBNTM8UQIkyDHPY5/aANdKraUTKDQUwlVxRiyD8U+PTSeD/BI56oOnuKrdFZQ==", "dev": true, "license": "MIT", "engines": { @@ -5356,274 +6029,308 @@ } }, "node_modules/@npmcli/agent": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@npmcli/agent/-/agent-3.0.0.tgz", - "integrity": "sha512-S79NdEgDQd/NGCay6TCoVzXSj74skRZIKJcpJjC5lOq34SZzyI6MqtiiWoiVWoVrTcGjNeC4ipbh1VIHlpfF5Q==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@npmcli/agent/-/agent-4.0.0.tgz", + "integrity": "sha512-kAQTcEN9E8ERLVg5AsGwLNoFb+oEG6engbqAU2P43gD4JEIkNGMHdVQ096FsOAAYpZPB0RSt0zgInKIAS1l5QA==", "dev": true, "license": "ISC", "dependencies": { "agent-base": "^7.1.0", "http-proxy-agent": "^7.0.0", "https-proxy-agent": "^7.0.1", - "lru-cache": "^10.0.1", + "lru-cache": "^11.2.1", "socks-proxy-agent": "^8.0.3" }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/@npmcli/agent/node_modules/lru-cache": { - "version": "10.4.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", - "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "version": "11.3.6", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.3.6.tgz", + "integrity": "sha512-Gf/KoL3C/MlI7Bt0PGI9I+TeTC/I6r/csU58N4BSNc4lppLBeKsOdFYkK+dX0ABDUMJNfCHTyPpzwwO21Awd3A==", "dev": true, - "license": "ISC" + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } }, "node_modules/@npmcli/fs": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-4.0.0.tgz", - "integrity": "sha512-/xGlezI6xfGO9NwuJlnwz/K14qD1kCSAGtacBHnGzeAIuJGazcp45KP5NuyARXoKb7cwulAGWVsbeSxdG/cb0Q==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-5.0.0.tgz", + "integrity": "sha512-7OsC1gNORBEawOa5+j2pXN9vsicaIOH5cPXxoR6fJOmH6/EXpJB2CajXOu1fPRFun2m1lktEFX11+P89hqO/og==", "dev": true, "license": "ISC", "dependencies": { "semver": "^7.3.5" }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/@npmcli/git": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/@npmcli/git/-/git-6.0.3.tgz", - "integrity": "sha512-GUYESQlxZRAdhs3UhbB6pVRNUELQOHXwK9ruDkwmCv2aZ5y0SApQzUJCg02p3A7Ue2J5hxvlk1YI53c00NmRyQ==", + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@npmcli/git/-/git-7.0.2.tgz", + "integrity": "sha512-oeolHDjExNAJAnlYP2qzNjMX/Xi9bmu78C9dIGr4xjobrSKbuMYCph8lTzn4vnW3NjIqVmw/f8BCfouqyJXlRg==", "dev": true, "license": "ISC", "dependencies": { - "@npmcli/promise-spawn": "^8.0.0", - "ini": "^5.0.0", - "lru-cache": "^10.0.1", - "npm-pick-manifest": "^10.0.0", - "proc-log": "^5.0.0", - "promise-retry": "^2.0.1", + "@gar/promise-retry": "^1.0.0", + "@npmcli/promise-spawn": "^9.0.0", + "ini": "^6.0.0", + "lru-cache": "^11.2.1", + "npm-pick-manifest": "^11.0.1", + "proc-log": "^6.0.0", "semver": "^7.3.5", - "which": "^5.0.0" + "which": "^6.0.0" }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@npmcli/git/node_modules/ini": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/ini/-/ini-6.0.0.tgz", + "integrity": "sha512-IBTdIkzZNOpqm7q3dRqJvMaldXjDHWkEDfrwGEQTs5eaQMWV+djAhR+wahyNNMAa+qpbDUhBMVt4ZKNwpPm7xQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/@npmcli/git/node_modules/isexe": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.5.tgz", - "integrity": "sha512-6B3tLtFqtQS4ekarvLVMZ+X+VlvQekbe4taUkf/rhVO3d/h0M2rfARm/pXLcPEsjjMsFgrFgSrhQIxcSVrBz8w==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-4.0.0.tgz", + "integrity": "sha512-FFUtZMpoZ8RqHS3XeXEmHWLA4thH+ZxCv2lOiPIn1Xc7CxrqhWzNSDzD+/chS/zbYezmiwWLdQC09JdQKmthOw==", "dev": true, "license": "BlueOak-1.0.0", "engines": { - "node": ">=18" + "node": ">=20" } }, "node_modules/@npmcli/git/node_modules/lru-cache": { - "version": "10.4.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", - "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "version": "11.3.6", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.3.6.tgz", + "integrity": "sha512-Gf/KoL3C/MlI7Bt0PGI9I+TeTC/I6r/csU58N4BSNc4lppLBeKsOdFYkK+dX0ABDUMJNfCHTyPpzwwO21Awd3A==", "dev": true, - "license": "ISC" + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/@npmcli/git/node_modules/proc-log": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-6.1.0.tgz", + "integrity": "sha512-iG+GYldRf2BQ0UDUAd6JQ/RwzaQy6mXmsk/IzlYyal4A4SNFw54MeH4/tLkF4I5WoWG9SQwuqWzS99jaFQHBuQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^20.17.0 || >=22.9.0" + } }, "node_modules/@npmcli/git/node_modules/which": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/which/-/which-5.0.0.tgz", - "integrity": "sha512-JEdGzHwwkrbWoGOlIHqQ5gtprKGOenpDHpxE9zVR1bWbOtYRyPPHMe9FaP6x61CmNaTThSkb0DAJte5jD+DmzQ==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/which/-/which-6.0.1.tgz", + "integrity": "sha512-oGLe46MIrCRqX7ytPUf66EAYvdeMIZYn3WaocqqKZAxrBpkqHfL/qvTyJ/bTk5+AqHCjXmrv3CEWgy368zhRUg==", "dev": true, "license": "ISC", "dependencies": { - "isexe": "^3.1.1" + "isexe": "^4.0.0" }, "bin": { "node-which": "bin/which.js" }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/@npmcli/installed-package-contents": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@npmcli/installed-package-contents/-/installed-package-contents-3.0.0.tgz", - "integrity": "sha512-fkxoPuFGvxyrH+OQzyTkX2LUEamrF4jZSmxjAtPPHHGO0dqsQ8tTKjnIS8SAnPHdk2I03BDtSMR5K/4loKg79Q==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@npmcli/installed-package-contents/-/installed-package-contents-4.0.0.tgz", + "integrity": "sha512-yNyAdkBxB72gtZ4GrwXCM0ZUedo9nIbOMKfGjt6Cu6DXf0p8y1PViZAKDC8q8kv/fufx0WTjRBdSlyrvnP7hmA==", "dev": true, "license": "ISC", "dependencies": { - "npm-bundled": "^4.0.0", - "npm-normalize-package-bin": "^4.0.0" + "npm-bundled": "^5.0.0", + "npm-normalize-package-bin": "^5.0.0" }, "bin": { "installed-package-contents": "bin/index.js" }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/@npmcli/node-gyp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@npmcli/node-gyp/-/node-gyp-4.0.0.tgz", - "integrity": "sha512-+t5DZ6mO/QFh78PByMq1fGSAub/agLJZDRfJRMeOSNCt8s9YVlTjmGpIPwPhvXTGUIJk+WszlT0rQa1W33yzNA==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@npmcli/node-gyp/-/node-gyp-5.0.0.tgz", + "integrity": "sha512-uuG5HZFXLfyFKqg8QypsmgLQW7smiRjVc45bqD/ofZZcR/uxEjgQU8qDPv0s9TEeMUiAAU/GC5bR6++UdTirIQ==", "dev": true, "license": "ISC", "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/@npmcli/package-json": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/@npmcli/package-json/-/package-json-6.2.0.tgz", - "integrity": "sha512-rCNLSB/JzNvot0SEyXqWZ7tX2B5dD2a1br2Dp0vSYVo5jh8Z0EZ7lS9TsZ1UtziddB1UfNUaMCc538/HztnJGA==", + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/@npmcli/package-json/-/package-json-7.0.5.tgz", + "integrity": "sha512-iVuTlG3ORq2iaVa1IWUxAO/jIp77tUKBhoMjuzYW2kL4MLN1bi/ofqkZ7D7OOwh8coAx1/S2ge0rMdGv8sLSOQ==", "dev": true, "license": "ISC", "dependencies": { - "@npmcli/git": "^6.0.0", - "glob": "^10.2.2", - "hosted-git-info": "^8.0.0", - "json-parse-even-better-errors": "^4.0.0", - "proc-log": "^5.0.0", + "@npmcli/git": "^7.0.0", + "glob": "^13.0.0", + "hosted-git-info": "^9.0.0", + "json-parse-even-better-errors": "^5.0.0", + "proc-log": "^6.0.0", "semver": "^7.5.3", - "validate-npm-package-license": "^3.0.4" + "spdx-expression-parse": "^4.0.0" }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@npmcli/package-json/node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/@npmcli/package-json/node_modules/brace-expansion": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz", + "integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" } }, "node_modules/@npmcli/package-json/node_modules/glob": { - "version": "10.5.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", - "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", - "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "version": "13.0.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-13.0.6.tgz", + "integrity": "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==", "dev": true, - "license": "ISC", + "license": "BlueOak-1.0.0", "dependencies": { - "foreground-child": "^3.1.0", - "jackspeak": "^3.1.2", - "minimatch": "^9.0.4", - "minipass": "^7.1.2", - "package-json-from-dist": "^1.0.0", - "path-scurry": "^1.11.1" + "minimatch": "^10.2.2", + "minipass": "^7.1.3", + "path-scurry": "^2.0.2" }, - "bin": { - "glob": "dist/esm/bin.mjs" + "engines": { + "node": "18 || 20 || >=22" }, "funding": { "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/@npmcli/package-json/node_modules/hosted-git-info": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-8.1.0.tgz", - "integrity": "sha512-Rw/B2DNQaPBICNXEm8balFz9a6WpZrkCGpcWFpy7nCj+NyhSdqXipmfvtmWt9xGfp0wZnBxB+iVpLmQMYt47Tw==", + "node_modules/@npmcli/package-json/node_modules/minimatch": { + "version": "10.2.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", + "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", "dev": true, - "license": "ISC", + "license": "BlueOak-1.0.0", "dependencies": { - "lru-cache": "^10.0.1" + "brace-expansion": "^5.0.5" }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/@npmcli/package-json/node_modules/lru-cache": { - "version": "10.4.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", - "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "node_modules/@npmcli/package-json/node_modules/proc-log": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-6.1.0.tgz", + "integrity": "sha512-iG+GYldRf2BQ0UDUAd6JQ/RwzaQy6mXmsk/IzlYyal4A4SNFw54MeH4/tLkF4I5WoWG9SQwuqWzS99jaFQHBuQ==", "dev": true, - "license": "ISC" + "license": "ISC", + "engines": { + "node": "^20.17.0 || >=22.9.0" + } }, "node_modules/@npmcli/promise-spawn": { - "version": "8.0.3", - "resolved": "https://registry.npmjs.org/@npmcli/promise-spawn/-/promise-spawn-8.0.3.tgz", - "integrity": "sha512-Yb00SWaL4F8w+K8YGhQ55+xE4RUNdMHV43WZGsiTM92gS+lC0mGsn7I4hLug7pbao035S6bj3Y3w0cUNGLfmkg==", + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/@npmcli/promise-spawn/-/promise-spawn-9.0.1.tgz", + "integrity": "sha512-OLUaoqBuyxeTqUvjA3FZFiXUfYC1alp3Sa99gW3EUDz3tZ3CbXDdcZ7qWKBzicrJleIgucoWamWH1saAmH/l2Q==", "dev": true, "license": "ISC", "dependencies": { - "which": "^5.0.0" + "which": "^6.0.0" }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/@npmcli/promise-spawn/node_modules/isexe": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.5.tgz", - "integrity": "sha512-6B3tLtFqtQS4ekarvLVMZ+X+VlvQekbe4taUkf/rhVO3d/h0M2rfARm/pXLcPEsjjMsFgrFgSrhQIxcSVrBz8w==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-4.0.0.tgz", + "integrity": "sha512-FFUtZMpoZ8RqHS3XeXEmHWLA4thH+ZxCv2lOiPIn1Xc7CxrqhWzNSDzD+/chS/zbYezmiwWLdQC09JdQKmthOw==", "dev": true, "license": "BlueOak-1.0.0", "engines": { - "node": ">=18" + "node": ">=20" } }, "node_modules/@npmcli/promise-spawn/node_modules/which": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/which/-/which-5.0.0.tgz", - "integrity": "sha512-JEdGzHwwkrbWoGOlIHqQ5gtprKGOenpDHpxE9zVR1bWbOtYRyPPHMe9FaP6x61CmNaTThSkb0DAJte5jD+DmzQ==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/which/-/which-6.0.1.tgz", + "integrity": "sha512-oGLe46MIrCRqX7ytPUf66EAYvdeMIZYn3WaocqqKZAxrBpkqHfL/qvTyJ/bTk5+AqHCjXmrv3CEWgy368zhRUg==", "dev": true, "license": "ISC", "dependencies": { - "isexe": "^3.1.1" + "isexe": "^4.0.0" }, "bin": { "node-which": "bin/which.js" }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/@npmcli/redact": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/@npmcli/redact/-/redact-3.2.2.tgz", - "integrity": "sha512-7VmYAmk4csGv08QzrDKScdzn11jHPFGyqJW39FyPgPuAp3zIaUmuCo1yxw9aGs+NEJuTGQ9Gwqpt93vtJubucg==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@npmcli/redact/-/redact-4.0.0.tgz", + "integrity": "sha512-gOBg5YHMfZy+TfHArfVogwgfBeQnKbbGo3pSUyK/gSI0AVu+pEiDVcKlQb0D8Mg1LNRZILZ6XG8I5dJ4KuAd9Q==", "dev": true, "license": "ISC", "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/@npmcli/run-script": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/@npmcli/run-script/-/run-script-9.1.0.tgz", - "integrity": "sha512-aoNSbxtkePXUlbZB+anS1LqsJdctG5n3UVhfU47+CDdwMi6uNTBMF9gPcQRnqghQd2FGzcwwIFBruFMxjhBewg==", + "version": "10.0.4", + "resolved": "https://registry.npmjs.org/@npmcli/run-script/-/run-script-10.0.4.tgz", + "integrity": "sha512-mGUWr1uMnf0le2TwfOZY4SFxZGXGfm4Jtay/nwAa2FLNAKXUoUwaGwBMNH36UHPtinWfTSJ3nqFQr0091CxVGg==", "dev": true, "license": "ISC", "dependencies": { - "@npmcli/node-gyp": "^4.0.0", - "@npmcli/package-json": "^6.0.0", - "@npmcli/promise-spawn": "^8.0.0", - "node-gyp": "^11.0.0", - "proc-log": "^5.0.0", - "which": "^5.0.0" + "@npmcli/node-gyp": "^5.0.0", + "@npmcli/package-json": "^7.0.0", + "@npmcli/promise-spawn": "^9.0.0", + "node-gyp": "^12.1.0", + "proc-log": "^6.0.0" }, "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/@npmcli/run-script/node_modules/isexe": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.5.tgz", - "integrity": "sha512-6B3tLtFqtQS4ekarvLVMZ+X+VlvQekbe4taUkf/rhVO3d/h0M2rfARm/pXLcPEsjjMsFgrFgSrhQIxcSVrBz8w==", - "dev": true, - "license": "BlueOak-1.0.0", - "engines": { - "node": ">=18" + "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/@npmcli/run-script/node_modules/which": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/which/-/which-5.0.0.tgz", - "integrity": "sha512-JEdGzHwwkrbWoGOlIHqQ5gtprKGOenpDHpxE9zVR1bWbOtYRyPPHMe9FaP6x61CmNaTThSkb0DAJte5jD+DmzQ==", + "node_modules/@npmcli/run-script/node_modules/proc-log": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-6.1.0.tgz", + "integrity": "sha512-iG+GYldRf2BQ0UDUAd6JQ/RwzaQy6mXmsk/IzlYyal4A4SNFw54MeH4/tLkF4I5WoWG9SQwuqWzS99jaFQHBuQ==", "dev": true, "license": "ISC", - "dependencies": { - "isexe": "^3.1.1" - }, - "bin": { - "node-which": "bin/which.js" - }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/@parcel/watcher": { @@ -5944,17 +6651,6 @@ "license": "MIT", "optional": true }, - "node_modules/@pkgjs/parseargs": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", - "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", - "dev": true, - "license": "MIT", - "optional": true, - "engines": { - "node": ">=14" - } - }, "node_modules/@primeuix/styled": { "version": "0.7.4", "resolved": "https://registry.npmjs.org/@primeuix/styled/-/styled-0.7.4.tgz", @@ -6037,9 +6733,9 @@ "license": "MIT" }, "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.52.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.52.3.tgz", - "integrity": "sha512-h6cqHGZ6VdnwliFG1NXvMPTy/9PS3h8oLh7ImwR+kl+oYnQizgjxsONmmPSb2C66RksfkfIxEVtDSEcJiO0tqw==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.59.0.tgz", + "integrity": "sha512-upnNBkA6ZH2VKGcBj9Fyl9IGNPULcjXRlg0LLeaioQWueH30p6IXtJEbKAgvyv+mJaMxSm1l6xwDXYjpEMiLMg==", "cpu": [ "arm" ], @@ -6051,9 +6747,9 @@ ] }, "node_modules/@rollup/rollup-android-arm64": { - "version": "4.52.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.52.3.tgz", - "integrity": "sha512-wd+u7SLT/u6knklV/ifG7gr5Qy4GUbH2hMWcDauPFJzmCZUAJ8L2bTkVXC2niOIxp8lk3iH/QX8kSrUxVZrOVw==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.59.0.tgz", + "integrity": "sha512-hZ+Zxj3SySm4A/DylsDKZAeVg0mvi++0PYVceVyX7hemkw7OreKdCvW2oQ3T1FMZvCaQXqOTHb8qmBShoqk69Q==", "cpu": [ "arm64" ], @@ -6065,9 +6761,9 @@ ] }, "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.52.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.52.3.tgz", - "integrity": "sha512-lj9ViATR1SsqycwFkJCtYfQTheBdvlWJqzqxwc9f2qrcVrQaF/gCuBRTiTolkRWS6KvNxSk4KHZWG7tDktLgjg==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.59.0.tgz", + "integrity": "sha512-W2Psnbh1J8ZJw0xKAd8zdNgF9HRLkdWwwdWqubSVk0pUuQkoHnv7rx4GiF9rT4t5DIZGAsConRE3AxCdJ4m8rg==", "cpu": [ "arm64" ], @@ -6079,9 +6775,9 @@ ] }, "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.52.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.52.3.tgz", - "integrity": "sha512-+Dyo7O1KUmIsbzx1l+4V4tvEVnVQqMOIYtrxK7ncLSknl1xnMHLgn7gddJVrYPNZfEB8CIi3hK8gq8bDhb3h5A==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.59.0.tgz", + "integrity": "sha512-ZW2KkwlS4lwTv7ZVsYDiARfFCnSGhzYPdiOU4IM2fDbL+QGlyAbjgSFuqNRbSthybLbIJ915UtZBtmuLrQAT/w==", "cpu": [ "x64" ], @@ -6093,9 +6789,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.52.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.52.3.tgz", - "integrity": "sha512-u9Xg2FavYbD30g3DSfNhxgNrxhi6xVG4Y6i9Ur1C7xUuGDW3banRbXj+qgnIrwRN4KeJ396jchwy9bCIzbyBEQ==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.59.0.tgz", + "integrity": "sha512-EsKaJ5ytAu9jI3lonzn3BgG8iRBjV4LxZexygcQbpiU0wU0ATxhNVEpXKfUa0pS05gTcSDMKpn3Sx+QB9RlTTA==", "cpu": [ "arm64" ], @@ -6107,9 +6803,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.52.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.52.3.tgz", - "integrity": "sha512-5M8kyi/OX96wtD5qJR89a/3x5x8x5inXBZO04JWhkQb2JWavOWfjgkdvUqibGJeNNaz1/Z1PPza5/tAPXICI6A==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.59.0.tgz", + "integrity": "sha512-d3DuZi2KzTMjImrxoHIAODUZYoUUMsuUiY4SRRcJy6NJoZ6iIqWnJu9IScV9jXysyGMVuW+KNzZvBLOcpdl3Vg==", "cpu": [ "x64" ], @@ -6121,13 +6817,16 @@ ] }, "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.52.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.52.3.tgz", - "integrity": "sha512-IoerZJ4l1wRMopEHRKOO16e04iXRDyZFZnNZKrWeNquh5d6bucjezgd+OxG03mOMTnS1x7hilzb3uURPkJ0OfA==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.59.0.tgz", + "integrity": "sha512-t4ONHboXi/3E0rT6OZl1pKbl2Vgxf9vJfWgmUoCEVQVxhW6Cw/c8I6hbbu7DAvgp82RKiH7TpLwxnJeKv2pbsw==", "cpu": [ "arm" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -6135,13 +6834,16 @@ ] }, "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.52.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.52.3.tgz", - "integrity": "sha512-ZYdtqgHTDfvrJHSh3W22TvjWxwOgc3ThK/XjgcNGP2DIwFIPeAPNsQxrJO5XqleSlgDux2VAoWQ5iJrtaC1TbA==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.59.0.tgz", + "integrity": "sha512-CikFT7aYPA2ufMD086cVORBYGHffBo4K8MQ4uPS/ZnY54GKj36i196u8U+aDVT2LX4eSMbyHtyOh7D7Zvk2VvA==", "cpu": [ "arm" ], "dev": true, + "libc": [ + "musl" + ], "license": "MIT", "optional": true, "os": [ @@ -6149,13 +6851,16 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.52.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.52.3.tgz", - "integrity": "sha512-NcViG7A0YtuFDA6xWSgmFb6iPFzHlf5vcqb2p0lGEbT+gjrEEz8nC/EeDHvx6mnGXnGCC1SeVV+8u+smj0CeGQ==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.59.0.tgz", + "integrity": "sha512-jYgUGk5aLd1nUb1CtQ8E+t5JhLc9x5WdBKew9ZgAXg7DBk0ZHErLHdXM24rfX+bKrFe+Xp5YuJo54I5HFjGDAA==", "cpu": [ "arm64" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -6163,13 +6868,16 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.52.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.52.3.tgz", - "integrity": "sha512-d3pY7LWno6SYNXRm6Ebsq0DJGoiLXTb83AIPCXl9fmtIQs/rXoS8SJxxUNtFbJ5MiOvs+7y34np77+9l4nfFMw==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.59.0.tgz", + "integrity": "sha512-peZRVEdnFWZ5Bh2KeumKG9ty7aCXzzEsHShOZEFiCQlDEepP1dpUl/SrUNXNg13UmZl+gzVDPsiCwnV1uI0RUA==", "cpu": [ "arm64" ], "dev": true, + "libc": [ + "musl" + ], "license": "MIT", "optional": true, "os": [ @@ -6177,13 +6885,33 @@ ] }, "node_modules/@rollup/rollup-linux-loong64-gnu": { - "version": "4.52.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.52.3.tgz", - "integrity": "sha512-3y5GA0JkBuirLqmjwAKwB0keDlI6JfGYduMlJD/Rl7fvb4Ni8iKdQs1eiunMZJhwDWdCvrcqXRY++VEBbvk6Eg==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.59.0.tgz", + "integrity": "sha512-gbUSW/97f7+r4gHy3Jlup8zDG190AuodsWnNiXErp9mT90iCy9NKKU0Xwx5k8VlRAIV2uU9CsMnEFg/xXaOfXg==", + "cpu": [ + "loong64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.59.0.tgz", + "integrity": "sha512-yTRONe79E+o0FWFijasoTjtzG9EBedFXJMl888NBEDCDV9I2wGbFFfJQQe63OijbFCUZqxpHz1GzpbtSFikJ4Q==", "cpu": [ "loong64" ], "dev": true, + "libc": [ + "musl" + ], "license": "MIT", "optional": true, "os": [ @@ -6191,13 +6919,33 @@ ] }, "node_modules/@rollup/rollup-linux-ppc64-gnu": { - "version": "4.52.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.52.3.tgz", - "integrity": "sha512-AUUH65a0p3Q0Yfm5oD2KVgzTKgwPyp9DSXc3UA7DtxhEb/WSPfbG4wqXeSN62OG5gSo18em4xv6dbfcUGXcagw==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.59.0.tgz", + "integrity": "sha512-sw1o3tfyk12k3OEpRddF68a1unZ5VCN7zoTNtSn2KndUE+ea3m3ROOKRCZxEpmT9nsGnogpFP9x6mnLTCaoLkA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.59.0.tgz", + "integrity": "sha512-+2kLtQ4xT3AiIxkzFVFXfsmlZiG5FXYW7ZyIIvGA7Bdeuh9Z0aN4hVyXS/G1E9bTP/vqszNIN/pUKCk/BTHsKA==", "cpu": [ "ppc64" ], "dev": true, + "libc": [ + "musl" + ], "license": "MIT", "optional": true, "os": [ @@ -6205,13 +6953,16 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.52.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.52.3.tgz", - "integrity": "sha512-1makPhFFVBqZE+XFg3Dkq+IkQ7JvmUrwwqaYBL2CE+ZpxPaqkGaiWFEWVGyvTwZace6WLJHwjVh/+CXbKDGPmg==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.59.0.tgz", + "integrity": "sha512-NDYMpsXYJJaj+I7UdwIuHHNxXZ/b/N2hR15NyH3m2qAtb/hHPA4g4SuuvrdxetTdndfj9b1WOmy73kcPRoERUg==", "cpu": [ "riscv64" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -6219,13 +6970,16 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-musl": { - "version": "4.52.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.52.3.tgz", - "integrity": "sha512-OOFJa28dxfl8kLOPMUOQBCO6z3X2SAfzIE276fwT52uXDWUS178KWq0pL7d6p1kz7pkzA0yQwtqL0dEPoVcRWg==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.59.0.tgz", + "integrity": "sha512-nLckB8WOqHIf1bhymk+oHxvM9D3tyPndZH8i8+35p/1YiVoVswPid2yLzgX7ZJP0KQvnkhM4H6QZ5m0LzbyIAg==", "cpu": [ "riscv64" ], "dev": true, + "libc": [ + "musl" + ], "license": "MIT", "optional": true, "os": [ @@ -6233,13 +6987,16 @@ ] }, "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.52.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.52.3.tgz", - "integrity": "sha512-jMdsML2VI5l+V7cKfZx3ak+SLlJ8fKvLJ0Eoa4b9/vCUrzXKgoKxvHqvJ/mkWhFiyp88nCkM5S2v6nIwRtPcgg==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.59.0.tgz", + "integrity": "sha512-oF87Ie3uAIvORFBpwnCvUzdeYUqi2wY6jRFWJAy1qus/udHFYIkplYRW+wo+GRUP4sKzYdmE1Y3+rY5Gc4ZO+w==", "cpu": [ "s390x" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -6247,13 +7004,16 @@ ] }, "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.52.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.52.3.tgz", - "integrity": "sha512-tPgGd6bY2M2LJTA1uGq8fkSPK8ZLYjDjY+ZLK9WHncCnfIz29LIXIqUgzCR0hIefzy6Hpbe8Th5WOSwTM8E7LA==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.59.0.tgz", + "integrity": "sha512-3AHmtQq/ppNuUspKAlvA8HtLybkDflkMuLK4DPo77DfthRb71V84/c4MlWJXixZz4uruIH4uaa07IqoAkG64fg==", "cpu": [ "x64" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -6261,23 +7021,40 @@ ] }, "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.52.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.52.3.tgz", - "integrity": "sha512-BCFkJjgk+WFzP+tcSMXq77ymAPIxsX9lFJWs+2JzuZTLtksJ2o5hvgTdIcZ5+oKzUDMwI0PfWzRBYAydAHF2Mw==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.59.0.tgz", + "integrity": "sha512-2UdiwS/9cTAx7qIUZB/fWtToJwvt0Vbo0zmnYt7ED35KPg13Q0ym1g442THLC7VyI6JfYTP4PiSOWyoMdV2/xg==", "cpu": [ "x64" ], "dev": true, + "libc": [ + "musl" + ], "license": "MIT", "optional": true, "os": [ "linux" ] }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.59.0.tgz", + "integrity": "sha512-M3bLRAVk6GOwFlPTIxVBSYKUaqfLrn8l0psKinkCFxl4lQvOSz8ZrKDz2gxcBwHFpci0B6rttydI4IpS4IS/jQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, "node_modules/@rollup/rollup-openharmony-arm64": { - "version": "4.52.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.52.3.tgz", - "integrity": "sha512-KTD/EqjZF3yvRaWUJdD1cW+IQBk4fbQaHYJUmP8N4XoKFZilVL8cobFSTDnjTtxWJQ3JYaMgF4nObY/+nYkumA==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.59.0.tgz", + "integrity": "sha512-tt9KBJqaqp5i5HUZzoafHZX8b5Q2Fe7UjYERADll83O4fGqJ49O1FsL6LpdzVFQcpwvnyd0i+K/VSwu/o/nWlA==", "cpu": [ "arm64" ], @@ -6289,9 +7066,9 @@ ] }, "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.52.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.52.3.tgz", - "integrity": "sha512-+zteHZdoUYLkyYKObGHieibUFLbttX2r+58l27XZauq0tcWYYuKUwY2wjeCN9oK1Um2YgH2ibd6cnX/wFD7DuA==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.59.0.tgz", + "integrity": "sha512-V5B6mG7OrGTwnxaNUzZTDTjDS7F75PO1ae6MJYdiMu60sq0CqN5CVeVsbhPxalupvTX8gXVSU9gq+Rx1/hvu6A==", "cpu": [ "arm64" ], @@ -6303,9 +7080,9 @@ ] }, "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.52.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.52.3.tgz", - "integrity": "sha512-of1iHkTQSo3kr6dTIRX6t81uj/c/b15HXVsPcEElN5sS859qHrOepM5p9G41Hah+CTqSh2r8Bm56dL2z9UQQ7g==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.59.0.tgz", + "integrity": "sha512-UKFMHPuM9R0iBegwzKF4y0C4J9u8C6MEJgFuXTBerMk7EJ92GFVFYBfOZaSGLu6COf7FxpQNqhNS4c4icUPqxA==", "cpu": [ "ia32" ], @@ -6317,9 +7094,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-gnu": { - "version": "4.52.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.52.3.tgz", - "integrity": "sha512-s0hybmlHb56mWVZQj8ra9048/WZTPLILKxcvcq+8awSZmyiSUZjjem1AhU3Tf4ZKpYhK4mg36HtHDOe8QJS5PQ==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.59.0.tgz", + "integrity": "sha512-laBkYlSS1n2L8fSo1thDNGrCTQMmxjYY5G0WFWjFFYZkKPjsMBsgJfGf4TLxXrF6RyhI60L8TMOjBMvXiTcxeA==", "cpu": [ "x64" ], @@ -6331,9 +7108,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.52.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.52.3.tgz", - "integrity": "sha512-zGIbEVVXVtauFgl3MRwGWEN36P5ZGenHRMgNw88X5wEhEBpq0XrMEZwOn07+ICrwM17XO5xfMZqh0OldCH5VTA==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.59.0.tgz", + "integrity": "sha512-2HRCml6OztYXyJXAvdDXPKcawukWY2GpR5/nxKp4iBgiO3wcoEGkAaqctIbZcNB6KlUQBIqt8VYkNSj2397EfA==", "cpu": [ "x64" ], @@ -6371,42 +7148,23 @@ "dev": true, "license": "MIT" }, - "node_modules/@rtsao/scc": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz", - "integrity": "sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==", - "dev": true, - "license": "MIT" - }, - "node_modules/@schematics/angular": { - "version": "20.3.12", - "resolved": "https://registry.npmjs.org/@schematics/angular/-/angular-20.3.12.tgz", - "integrity": "sha512-ikl+nkWUab/Z4eSkBHgq9FLIUH8qh4OcYKeBQ0fyWqIUFHyjjK0JOfwmH1g/3zAmuUMtkthHCehAtyKzCTQjVA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@angular-devkit/core": "20.3.12", - "@angular-devkit/schematics": "20.3.12", - "jsonc-parser": "3.3.1" - }, - "engines": { - "node": "^20.19.0 || ^22.12.0 || >=24.0.0", - "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", - "yarn": ">= 1.13.0" - } - }, - "node_modules/@schematics/angular/node_modules/@angular-devkit/schematics": { - "version": "20.3.12", - "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-20.3.12.tgz", - "integrity": "sha512-JqJ1u59y+Ud51k/8MHYzSP+aQOeC2PJBaDmMnvqfWVaIt6n3x4gc/VtuhqhpJ0SKulbFuOWgAfI6QbPFrgUYQQ==", + "node_modules/@rtsao/scc": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz", + "integrity": "sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==", + "dev": true, + "license": "MIT" + }, + "node_modules/@schematics/angular": { + "version": "20.3.25", + "resolved": "https://registry.npmjs.org/@schematics/angular/-/angular-20.3.25.tgz", + "integrity": "sha512-ezoJpPKhhjXgE3LxuNcJO/ghSXs8f73xrqGvqouag7IbuliJYbHrt22WH3X75XSbD0+iOdiqA2bARvAO/ezyxQ==", "dev": true, "license": "MIT", "dependencies": { - "@angular-devkit/core": "20.3.12", - "jsonc-parser": "3.3.1", - "magic-string": "0.30.17", - "ora": "8.2.0", - "rxjs": "7.8.2" + "@angular-devkit/core": "20.3.25", + "@angular-devkit/schematics": "20.3.25", + "jsonc-parser": "3.3.1" }, "engines": { "node": "^20.19.0 || ^22.12.0 || >=24.0.0", @@ -6414,43 +7172,33 @@ "yarn": ">= 1.13.0" } }, - "node_modules/@schematics/angular/node_modules/rxjs": { - "version": "7.8.2", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz", - "integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.1.0" - } - }, "node_modules/@sigstore/bundle": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@sigstore/bundle/-/bundle-3.1.0.tgz", - "integrity": "sha512-Mm1E3/CmDDCz3nDhFKTuYdB47EdRFRQMOE/EAbiG1MJW77/w1b3P7Qx7JSrVJs8PfwOLOVcKQCHErIwCTyPbag==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@sigstore/bundle/-/bundle-4.0.0.tgz", + "integrity": "sha512-NwCl5Y0V6Di0NexvkTqdoVfmjTaQwoLM236r89KEojGmq/jMls8S+zb7yOwAPdXvbwfKDlP+lmXgAL4vKSQT+A==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@sigstore/protobuf-specs": "^0.4.0" + "@sigstore/protobuf-specs": "^0.5.0" }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/@sigstore/core": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@sigstore/core/-/core-2.0.0.tgz", - "integrity": "sha512-nYxaSb/MtlSI+JWcwTHQxyNmWeWrUXJJ/G4liLrGG7+tS4vAz6LF3xRXqLH6wPIVUoZQel2Fs4ddLx4NCpiIYg==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@sigstore/core/-/core-3.2.0.tgz", + "integrity": "sha512-kxHrDQ9YgfrWUSXU0cjsQGv8JykOFZQ9ErNKbFPWzk3Hgpwu8x2hHrQ9IdA8yl+j9RTLTC3sAF3Tdq1IQCP4oA==", "dev": true, "license": "Apache-2.0", "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/@sigstore/protobuf-specs": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/@sigstore/protobuf-specs/-/protobuf-specs-0.4.3.tgz", - "integrity": "sha512-fk2zjD9117RL9BjqEwF7fwv7Q/P9yGsMV4MUJZ/DocaQJ6+3pKr+syBq1owU5Q5qGw5CUbXzm+4yJ2JVRDQeSA==", + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/@sigstore/protobuf-specs/-/protobuf-specs-0.5.1.tgz", + "integrity": "sha512-/ScWUhhoFasJsSRGTVBwId1loQjjnjAfE4djL6ZhrXRpNCmPTnUKF5Jokd58ILseOMjzET3UrMOtJPS9sYeI0g==", "dev": true, "license": "Apache-2.0", "engines": { @@ -6458,50 +7206,60 @@ } }, "node_modules/@sigstore/sign": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@sigstore/sign/-/sign-3.1.0.tgz", - "integrity": "sha512-knzjmaOHOov1Ur7N/z4B1oPqZ0QX5geUfhrVaqVlu+hl0EAoL4o+l0MSULINcD5GCWe3Z0+YJO8ues6vFlW0Yw==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/@sigstore/sign/-/sign-4.1.1.tgz", + "integrity": "sha512-Hf4xglukg0XXQ2RiD5vSoLjdPe8OBUPA8XeVjUObheuDcWdYWrnH/BNmxZCzkAy68MzmNCxXLeurJvs6hcP2OQ==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@sigstore/bundle": "^3.1.0", - "@sigstore/core": "^2.0.0", - "@sigstore/protobuf-specs": "^0.4.0", - "make-fetch-happen": "^14.0.2", - "proc-log": "^5.0.0", - "promise-retry": "^2.0.1" + "@gar/promise-retry": "^1.0.2", + "@sigstore/bundle": "^4.0.0", + "@sigstore/core": "^3.2.0", + "@sigstore/protobuf-specs": "^0.5.0", + "make-fetch-happen": "^15.0.4", + "proc-log": "^6.1.0" }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@sigstore/sign/node_modules/proc-log": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-6.1.0.tgz", + "integrity": "sha512-iG+GYldRf2BQ0UDUAd6JQ/RwzaQy6mXmsk/IzlYyal4A4SNFw54MeH4/tLkF4I5WoWG9SQwuqWzS99jaFQHBuQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/@sigstore/tuf": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/@sigstore/tuf/-/tuf-3.1.1.tgz", - "integrity": "sha512-eFFvlcBIoGwVkkwmTi/vEQFSva3xs5Ot3WmBcjgjVdiaoelBLQaQ/ZBfhlG0MnG0cmTYScPpk7eDdGDWUcFUmg==", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@sigstore/tuf/-/tuf-4.0.2.tgz", + "integrity": "sha512-TCAzTy0xzdP79EnxSjq9KQ3eaR7+FmudLC6eRKknVKZbV7ZNlGLClAAQb/HMNJ5n2OBNk2GT1tEmU0xuPr+SLQ==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@sigstore/protobuf-specs": "^0.4.1", - "tuf-js": "^3.0.1" + "@sigstore/protobuf-specs": "^0.5.0", + "tuf-js": "^4.1.0" }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/@sigstore/verify": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/@sigstore/verify/-/verify-2.1.1.tgz", - "integrity": "sha512-hVJD77oT67aowHxwT4+M6PGOp+E2LtLdTK3+FC0lBO9T7sYwItDMXZ7Z07IDCvR1M717a4axbIWckrW67KMP/w==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@sigstore/verify/-/verify-3.1.0.tgz", + "integrity": "sha512-mNe0Iigql08YupSOGv197YdHpPPr+EzDZmfCgMc7RPNaZTw5aLN01nBl6CHJOh3BGtnMIj83EeN4butBchc8Ag==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@sigstore/bundle": "^3.1.0", - "@sigstore/core": "^2.0.0", - "@sigstore/protobuf-specs": "^0.4.1" + "@sigstore/bundle": "^4.0.0", + "@sigstore/core": "^3.1.0", + "@sigstore/protobuf-specs": "^0.5.0" }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/@sindresorhus/base62": { @@ -6563,17 +7321,56 @@ } }, "node_modules/@tufjs/models": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@tufjs/models/-/models-3.0.1.tgz", - "integrity": "sha512-UUYHISyhCU3ZgN8yaear3cGATHb3SMuKHsQ/nVbHXcmnBf+LzQ/cQfhNG+rfaSHgqGKNEm2cOCLVLELStUQ1JA==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@tufjs/models/-/models-4.1.0.tgz", + "integrity": "sha512-Y8cK9aggNRsqJVaKUlEYs4s7CvQ1b1ta2DVPyAimb0I2qhzjNk+A+mxvll/klL0RlfuIUei8BF7YWiua4kQqww==", "dev": true, "license": "MIT", "dependencies": { "@tufjs/canonical-json": "2.0.0", - "minimatch": "^9.0.5" + "minimatch": "^10.1.1" }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@tufjs/models/node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/@tufjs/models/node_modules/brace-expansion": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz", + "integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/@tufjs/models/node_modules/minimatch": { + "version": "10.2.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", + "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.5" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, "node_modules/@types/body-parser": { @@ -6720,16 +7517,6 @@ "dev": true, "license": "MIT" }, - "node_modules/@types/jasminewd2": { - "version": "2.0.10", - "resolved": "https://registry.npmjs.org/@types/jasminewd2/-/jasminewd2-2.0.10.tgz", - "integrity": "sha512-J7mDz7ovjwjc+Y9rR9rY53hFWKATcIkrr9DwQWmOas4/pnIPJTXawnzjwpHm3RSxz/e3ZVUvQ7cRbd5UQLo10g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/jasmine": "*" - } - }, "node_modules/@types/json-schema": { "version": "7.0.15", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", @@ -6778,13 +7565,6 @@ "@types/node": "*" } }, - "node_modules/@types/q": { - "version": "0.0.32", - "resolved": "https://registry.npmjs.org/@types/q/-/q-0.0.32.tgz", - "integrity": "sha512-qYi3YV9inU/REEfxwVcGZzbS3KG/Xs90lv0Pr+lDtuVjBPGd1A+eciXzVSaRvLify132BfcvhvEjeVahrUl0Ug==", - "dev": true, - "license": "MIT" - }, "node_modules/@types/qs": { "version": "6.14.0", "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.14.0.tgz", @@ -6813,13 +7593,6 @@ "dev": true, "license": "MIT" }, - "node_modules/@types/selenium-webdriver": { - "version": "3.0.26", - "resolved": "https://registry.npmjs.org/@types/selenium-webdriver/-/selenium-webdriver-3.0.26.tgz", - "integrity": "sha512-dyIGFKXfUFiwkMfNGn1+F6b80ZjR3uSYv1j6xVJSDlft5waZ2cwkHW4e7zNzvq7hiEackcgvBpmnXZrI1GltPg==", - "dev": true, - "license": "MIT" - }, "node_modules/@types/send": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/@types/send/-/send-1.2.1.tgz", @@ -7372,19 +8145,20 @@ "license": "BSD-2-Clause" }, "node_modules/abbrev": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-3.0.1.tgz", - "integrity": "sha512-AO2ac6pjRB3SJmGJo+v5/aK6Omggp6fsLrs6wN9bd35ulu4cCwaAU9+7ZhXjeqHVkaHThLuzH0nZr0YpCDhygg==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-4.0.0.tgz", + "integrity": "sha512-a1wflyaL0tHtJSmLSOVybYhy22vRih4eduhhrkcjgrWGnRfrZtovJ2FRjxuTtkkj47O/baf0R86QU5OuYpz8fA==", "dev": true, "license": "ISC", "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/accepts": { "version": "1.3.8", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "dev": true, "license": "MIT", "dependencies": { "mime-types": "~2.1.34", @@ -7481,16 +8255,6 @@ "node": ">=0.8" } }, - "node_modules/adm-zip": { - "version": "0.5.16", - "resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.5.16.tgz", - "integrity": "sha512-TGw5yVi4saajsSEgz25grObGHEUaDrniwvA2qwSC060KfqGPdglhvPMA2lPIoxs3PQIItj2iag35fONcQqgUaQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12.0" - } - }, "node_modules/agent-base": { "version": "7.1.4", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", @@ -7502,9 +8266,9 @@ } }, "node_modules/ajv": { - "version": "8.17.1", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", - "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", + "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", "dev": true, "license": "MIT", "dependencies": { @@ -7655,9 +8419,9 @@ } }, "node_modules/anymatch/node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", "dev": true, "license": "MIT", "engines": { @@ -7710,15 +8474,6 @@ "node": ">=0.10.0" } }, - "node_modules/arr-flatten": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz", - "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/arr-union": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", @@ -7774,29 +8529,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/array-union": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz", - "integrity": "sha512-Dxr6QJj/RdU/hCaBjOfxW+q6lyuVE6JFWIrAUpuOOhoJJoQ99cUn3igRaHVB5P9WrgFVN0FfArM3x0cueOU8ng==", - "dev": true, - "license": "MIT", - "dependencies": { - "array-uniq": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/array-uniq": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz", - "integrity": "sha512-MNha4BWQ6JbwhFhj03YK552f7cb3AzoE8SzeljgChvL1dl3IcvggXVz1DilzySZkCja+CXuZbdW7yATchWn8/Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/array-unique": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", @@ -7888,36 +8620,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/arrify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", - "integrity": "sha512-3CYzex9M9FGQjCGMGyi6/31c8GJbgb0qGyrx5HWxPd0aCwh4cB2YjMb2Xf9UuoogrMrlO9cTqnB5rI5GHZTcUA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/asn1": { - "version": "0.2.6", - "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz", - "integrity": "sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "safer-buffer": "~2.1.0" - } - }, - "node_modules/assert-plus": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.8" - } - }, "node_modules/assign-symbols": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", @@ -7943,13 +8645,6 @@ "integrity": "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==", "license": "MIT" }, - "node_modules/asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", - "dev": true, - "license": "MIT" - }, "node_modules/atob": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", @@ -8015,23 +8710,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/aws-sign2": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", - "integrity": "sha512-08kcGqnYf/YmjoRhfxyu+CLxBjUtHLXLXX/vUfx9l2LYzG3c1m61nrpyFUZI6zeS+Li/wWMMidD9KgrqtGq3mA==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "*" - } - }, - "node_modules/aws4": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.13.2.tgz", - "integrity": "sha512-lHe62zvbTB5eEABUVi/AwVh0ZKY9rMMDhmm+eeyuuUQbQ3+J+fONVQOZyj+DdrvD4BY33uYniyRJ4UJIaSKAfw==", - "dev": true, - "license": "MIT" - }, "node_modules/axobject-query": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz", @@ -8183,16 +8861,6 @@ "dev": true, "license": "MIT" }, - "node_modules/bcrypt-pbkdf": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", - "integrity": "sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "tweetnacl": "^0.14.3" - } - }, "node_modules/beasties": { "version": "0.3.5", "resolved": "https://registry.npmjs.org/beasties/-/beasties-0.3.5.tgz", @@ -8235,22 +8903,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/blocking-proxy": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/blocking-proxy/-/blocking-proxy-1.0.1.tgz", - "integrity": "sha512-KE8NFMZr3mN2E0HcvCgRtX7DjhiIQrwle+nSVJVC/yqFb9+xznHl2ZcoBp2L9qzkI4t4cBFJ1efXF8Dwi132RA==", - "dev": true, - "license": "MIT", - "dependencies": { - "minimist": "^1.2.0" - }, - "bin": { - "blocking-proxy": "built/lib/bin.js" - }, - "engines": { - "node": ">=6.9.x" - } - }, "node_modules/body-parser": { "version": "2.2.2", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.2.tgz", @@ -8276,16 +8928,6 @@ "url": "https://opencollective.com/express" } }, - "node_modules/body-parser/node_modules/bytes": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", - "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, "node_modules/bonjour-service": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/bonjour-service/-/bonjour-service-1.3.0.tgz", @@ -8311,9 +8953,9 @@ "license": "MIT" }, "node_modules/brace-expansion": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", - "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.0.tgz", + "integrity": "sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==", "dev": true, "license": "MIT", "dependencies": { @@ -8324,94 +8966,46 @@ "version": "3.0.3", "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", - "dev": true, "license": "MIT", "dependencies": { "fill-range": "^7.1.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/browserslist": { - "version": "4.28.1", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz", - "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "baseline-browser-mapping": "^2.9.0", - "caniuse-lite": "^1.0.30001759", - "electron-to-chromium": "^1.5.263", - "node-releases": "^2.0.27", - "update-browserslist-db": "^1.2.0" - }, - "bin": { - "browserslist": "cli.js" - }, - "engines": { - "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" - } - }, - "node_modules/browserstack": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/browserstack/-/browserstack-1.6.1.tgz", - "integrity": "sha512-GxtFjpIaKdbAyzHfFDKixKO8IBT7wR3NjbzrGc78nNs/Ciys9wU3/nBtsqsWv5nDSrdI5tz0peKuzCPuNXNUiw==", - "dev": true, - "license": "MIT", - "dependencies": { - "https-proxy-agent": "^2.2.1" - } - }, - "node_modules/browserstack/node_modules/agent-base": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.3.0.tgz", - "integrity": "sha512-salcGninV0nPrwpGNn4VTXBb1SOuXQBiqbrNXoeizJsHrsL6ERFM2Ne3JUSBWRE6aeNJI2ROP/WEEIDUiDe3cg==", - "dev": true, - "license": "MIT", - "dependencies": { - "es6-promisify": "^5.0.0" - }, - "engines": { - "node": ">= 4.0.0" - } - }, - "node_modules/browserstack/node_modules/debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.1" + }, + "engines": { + "node": ">=8" } }, - "node_modules/browserstack/node_modules/https-proxy-agent": { - "version": "2.2.4", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-2.2.4.tgz", - "integrity": "sha512-OmvfoQ53WLjtA9HeYP9RNrWMJzzAz1JGaSFr1nijg0PVR1JaD/xbJq1mdEIIlxGpXp9eSe/O2LgU9DJmTPd0Eg==", + "node_modules/browserslist": { + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz", + "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==", "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], "license": "MIT", "dependencies": { - "agent-base": "^4.3.0", - "debug": "^3.1.0" + "baseline-browser-mapping": "^2.9.0", + "caniuse-lite": "^1.0.30001759", + "electron-to-chromium": "^1.5.263", + "node-releases": "^2.0.27", + "update-browserslist-db": "^1.2.0" + }, + "bin": { + "browserslist": "cli.js" }, "engines": { - "node": ">= 4.5.0" + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" } }, "node_modules/btoa": { @@ -8458,102 +9052,101 @@ } }, "node_modules/bytes": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", - "integrity": "sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", "license": "MIT", "engines": { "node": ">= 0.8" } }, "node_modules/cacache": { - "version": "19.0.1", - "resolved": "https://registry.npmjs.org/cacache/-/cacache-19.0.1.tgz", - "integrity": "sha512-hdsUxulXCi5STId78vRVYEtDAjq99ICAUktLTeTYsLoTE6Z8dS0c8pWNCxwdrk9YfJeobDZc2Y186hD/5ZQgFQ==", + "version": "20.0.4", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-20.0.4.tgz", + "integrity": "sha512-M3Lab8NPYlZU2exsL3bMVvMrMqgwCnMWfdZbK28bn3pK6APT/Te/I8hjRPNu1uwORY9a1eEQoifXbKPQMfMTOA==", "dev": true, "license": "ISC", "dependencies": { - "@npmcli/fs": "^4.0.0", + "@npmcli/fs": "^5.0.0", "fs-minipass": "^3.0.0", - "glob": "^10.2.2", - "lru-cache": "^10.0.1", + "glob": "^13.0.0", + "lru-cache": "^11.1.0", "minipass": "^7.0.3", "minipass-collect": "^2.0.1", "minipass-flush": "^1.0.5", "minipass-pipeline": "^1.2.4", "p-map": "^7.0.2", - "ssri": "^12.0.0", - "tar": "^7.4.3", - "unique-filename": "^4.0.0" + "ssri": "^13.0.0" }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/cacache/node_modules/chownr": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz", - "integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==", + "node_modules/cacache/node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", "dev": true, - "license": "BlueOak-1.0.0", + "license": "MIT", "engines": { - "node": ">=18" + "node": "18 || 20 || >=22" + } + }, + "node_modules/cacache/node_modules/brace-expansion": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz", + "integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" } }, "node_modules/cacache/node_modules/glob": { - "version": "10.5.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", - "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", - "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "version": "13.0.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-13.0.6.tgz", + "integrity": "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==", "dev": true, - "license": "ISC", + "license": "BlueOak-1.0.0", "dependencies": { - "foreground-child": "^3.1.0", - "jackspeak": "^3.1.2", - "minimatch": "^9.0.4", - "minipass": "^7.1.2", - "package-json-from-dist": "^1.0.0", - "path-scurry": "^1.11.1" + "minimatch": "^10.2.2", + "minipass": "^7.1.3", + "path-scurry": "^2.0.2" }, - "bin": { - "glob": "dist/esm/bin.mjs" + "engines": { + "node": "18 || 20 || >=22" }, "funding": { "url": "https://github.com/sponsors/isaacs" } }, "node_modules/cacache/node_modules/lru-cache": { - "version": "10.4.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", - "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/cacache/node_modules/tar": { - "version": "7.5.7", - "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.7.tgz", - "integrity": "sha512-fov56fJiRuThVFXD6o6/Q354S7pnWMJIVlDBYijsTNx6jKSE4pvrDTs6lUnmGvNyfJwFQQwWy3owKz1ucIhveQ==", + "version": "11.3.6", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.3.6.tgz", + "integrity": "sha512-Gf/KoL3C/MlI7Bt0PGI9I+TeTC/I6r/csU58N4BSNc4lppLBeKsOdFYkK+dX0ABDUMJNfCHTyPpzwwO21Awd3A==", "dev": true, "license": "BlueOak-1.0.0", - "dependencies": { - "@isaacs/fs-minipass": "^4.0.0", - "chownr": "^3.0.0", - "minipass": "^7.1.2", - "minizlib": "^3.1.0", - "yallist": "^5.0.0" - }, "engines": { - "node": ">=18" + "node": "20 || >=22" } }, - "node_modules/cacache/node_modules/yallist": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz", - "integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==", + "node_modules/cacache/node_modules/minimatch": { + "version": "10.2.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", + "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", "dev": true, "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.5" + }, "engines": { - "node": ">=18" + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, "node_modules/cache-base": { @@ -8633,16 +9226,6 @@ "node": ">=6" } }, - "node_modules/camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, "node_modules/camelize": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/camelize/-/camelize-1.0.0.tgz", @@ -8690,13 +9273,6 @@ "node": ">=10.0.0" } }, - "node_modules/caseless": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", - "integrity": "sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==", - "dev": true, - "license": "Apache-2.0" - }, "node_modules/cfb": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/cfb/-/cfb-1.2.2.tgz", @@ -8880,13 +9456,13 @@ } }, "node_modules/chownr": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", - "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz", + "integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==", "dev": true, - "license": "ISC", + "license": "BlueOak-1.0.0", "engines": { - "node": ">=10" + "node": ">=18" } }, "node_modules/chrome-trace-event": { @@ -9168,19 +9744,6 @@ "node": ">=0.1.90" } }, - "node_modules/combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "dev": true, - "license": "MIT", - "dependencies": { - "delayed-stream": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, "node_modules/commander": { "version": "14.0.3", "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.3.tgz", @@ -9230,17 +9793,17 @@ } }, "node_modules/compression": { - "version": "1.7.3", - "resolved": "https://registry.npmjs.org/compression/-/compression-1.7.3.tgz", - "integrity": "sha512-HSjyBG5N1Nnz7tF2+O7A9XUhyjru71/fwgNb7oIsEVHR0WShfs2tIS/EySLgiTe98aOK18YDlMXpzjCXY/n9mg==", + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/compression/-/compression-1.8.1.tgz", + "integrity": "sha512-9mAqGPHLakhCLeNyxPkK4xVo746zQ/czLH1Ky+vkitMnWfWZps8r0qXuwhwizagCRttsL4lfG4pIOvaWLpAP0w==", "license": "MIT", "dependencies": { - "accepts": "~1.3.5", - "bytes": "3.0.0", - "compressible": "~2.0.14", + "bytes": "3.1.2", + "compressible": "~2.0.18", "debug": "2.6.9", - "on-headers": "~1.0.1", - "safe-buffer": "5.1.2", + "negotiator": "~0.6.4", + "on-headers": "~1.1.0", + "safe-buffer": "5.2.1", "vary": "~1.1.2" }, "engines": { @@ -9262,6 +9825,35 @@ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", "license": "MIT" }, + "node_modules/compression/node_modules/negotiator": { + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.4.tgz", + "integrity": "sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/compression/node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -9379,9 +9971,9 @@ } }, "node_modules/content-disposition": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.1.tgz", - "integrity": "sha512-oIXISMynqSqm241k6kcQ5UwttDILMK4BiurCfGEREw6+X9jkkpEe5T9FZaApyLGGOnFuyMWZpdolTXMtvEJ08Q==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.1.0.tgz", + "integrity": "sha512-5jRCH9Z/+DRP7rkvY83B+yGIGX96OYdJmzngqnw2SBSxqCFPd0w2km3s5iawpGX8krnwSGmF0FW5Nhr0Hfai3g==", "dev": true, "license": "MIT", "engines": { @@ -9461,20 +10053,20 @@ } }, "node_modules/copy-webpack-plugin": { - "version": "13.0.1", - "resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-13.0.1.tgz", - "integrity": "sha512-J+YV3WfhY6W/Xf9h+J1znYuqTye2xkBUIGyTPWuBAT27qajBa5mR4f8WBmfDY3YjRftT2kqZZiLi1qf0H+UOFw==", + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-14.0.0.tgz", + "integrity": "sha512-3JLW90aBGeaTLpM7mYQKpnVdgsUZRExY55giiZgLuX/xTQRUs1dOCwbBnWnvY6Q6rfZoXMNwzOQJCSZPppfqXA==", "dev": true, "license": "MIT", "dependencies": { "glob-parent": "^6.0.1", "normalize-path": "^3.0.0", "schema-utils": "^4.2.0", - "serialize-javascript": "^6.0.2", + "serialize-javascript": "^7.0.3", "tinyglobby": "^0.2.12" }, "engines": { - "node": ">= 18.12.0" + "node": ">= 20.9.0" }, "funding": { "type": "opencollective", @@ -9484,6 +10076,16 @@ "webpack": "^5.1.0" } }, + "node_modules/copy-webpack-plugin/node_modules/serialize-javascript": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-7.0.5.tgz", + "integrity": "sha512-F4LcB0UqUl1zErq+1nYEEzSHJnIwb3AF2XWB94b+afhrekOUijwooAYqFyRbjYkm2PAKBabx6oYv/xDxNi8IBw==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=20.0.0" + } + }, "node_modules/core-js": { "version": "3.48.0", "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.48.0.tgz", @@ -9589,9 +10191,9 @@ } }, "node_modules/cpx-fixed/node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz", + "integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==", "dev": true, "license": "MIT", "dependencies": { @@ -9610,9 +10212,9 @@ } }, "node_modules/cpx-fixed/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", "dev": true, "license": "ISC", "dependencies": { @@ -9757,19 +10359,6 @@ "dev": true, "license": "MIT" }, - "node_modules/dashdash": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", - "integrity": "sha512-jRFi8UDGo6j+odZiEpjazZaWqEal3w/basFjQHQEwVtZJGDpxbH1MeYluwCS8Xq5wmLJooDlMgvVarmWfGM44g==", - "dev": true, - "license": "MIT", - "dependencies": { - "assert-plus": "^1.0.0" - }, - "engines": { - "node": ">=0.10" - } - }, "node_modules/dasherize": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/dasherize/-/dasherize-2.0.0.tgz", @@ -9864,16 +10453,6 @@ } } }, - "node_modules/decamelize": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", - "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/decode-uri-component": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.2.tgz", @@ -10024,49 +10603,6 @@ "node": ">=0.10.0" } }, - "node_modules/del": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/del/-/del-2.2.2.tgz", - "integrity": "sha512-Z4fzpbIRjOu7lO5jCETSWoqUDVe0IPOlfugBsF6suen2LKDlVb4QZpKEM9P+buNJ4KI1eN7I083w/pbKUpsrWQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "globby": "^5.0.0", - "is-path-cwd": "^1.0.0", - "is-path-in-cwd": "^1.0.0", - "object-assign": "^4.0.1", - "pify": "^2.0.0", - "pinkie-promise": "^2.0.0", - "rimraf": "^2.2.8" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/del/node_modules/rimraf": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", - "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", - "deprecated": "Rimraf versions prior to v4 are no longer supported", - "dev": true, - "license": "ISC", - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - } - }, - "node_modules/delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.4.0" - } - }, "node_modules/depd": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", @@ -10244,9 +10780,9 @@ } }, "node_modules/dompurify": { - "version": "2.5.8", - "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-2.5.8.tgz", - "integrity": "sha512-o1vSNgrmYMQObbSSvF/1brBYEQPHhV1+gsmrusO7/GXtp1T9rCS8cXFqVxK/9crT1jA6Ccv+5MTSjBNqr7Sovw==", + "version": "2.5.9", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-2.5.9.tgz", + "integrity": "sha512-i6mvVmWN4xo9LrhCOZrDgSs9noW6nOahbrmzjRbPF36YPyj5Ue5lgok0MHDWkG7xzpWFO2OYttXdzM7rJxHvNA==", "license": "(MPL-2.0 OR Apache-2.0)", "optional": true }, @@ -10295,24 +10831,6 @@ "dev": true, "license": "MIT" }, - "node_modules/eastasianwidth": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", - "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", - "dev": true, - "license": "MIT" - }, - "node_modules/ecc-jsbn": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", - "integrity": "sha512-eh9O+hwRHNbG4BLTjEl3nw044CkGm5X6LoaCf7LPp7UU8Qrt47JYNi6nPX8xjW97TKGKm1ouctg0QSpZe9qrnw==", - "dev": true, - "license": "MIT", - "dependencies": { - "jsbn": "~0.1.0", - "safer-buffer": "^2.1.0" - } - }, "node_modules/ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", @@ -10353,31 +10871,6 @@ "node": ">= 0.8" } }, - "node_modules/encoding": { - "version": "0.1.13", - "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz", - "integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "iconv-lite": "^0.6.2" - } - }, - "node_modules/encoding/node_modules/iconv-lite": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", - "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/end-of-stream": { "version": "1.4.5", "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", @@ -10782,9 +11275,9 @@ } }, "node_modules/esbuild-wasm": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/esbuild-wasm/-/esbuild-wasm-0.25.9.tgz", - "integrity": "sha512-Jpv5tCSwQg18aCqCRD3oHIX/prBhXMDapIoG//A+6+dV0e7KQMGFg85ihJ5T1EeMjbZjON3TqFy0VrGAnIHLDA==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/esbuild-wasm/-/esbuild-wasm-0.28.0.tgz", + "integrity": "sha512-5TRVKExcEmeMkccIZMzUq+Az6X2RoMAJyfl6SMMO1dMVhmvt0I2mx7gAb6zYi42n4d1ETcatFXazGKzA+aW7fg==", "dev": true, "license": "MIT", "bin": { @@ -11010,9 +11503,9 @@ } }, "node_modules/eslint-plugin-import/node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz", + "integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==", "dev": true, "license": "MIT", "dependencies": { @@ -11044,9 +11537,9 @@ } }, "node_modules/eslint-plugin-import/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", "dev": true, "license": "ISC", "dependencies": { @@ -11189,9 +11682,9 @@ } }, "node_modules/eslint/node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz", + "integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==", "dev": true, "license": "MIT", "dependencies": { @@ -11206,9 +11699,9 @@ } }, "node_modules/eslint/node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz", + "integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==", "dev": true, "license": "MIT", "dependencies": { @@ -11251,9 +11744,9 @@ "license": "MIT" }, "node_modules/eslint/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", "dev": true, "license": "ISC", "dependencies": { @@ -11385,24 +11878,15 @@ } }, "node_modules/eventsource-parser": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.0.6.tgz", - "integrity": "sha512-Vo1ab+QXPzZ4tCa8SwIHJFaSzy4R6SHf7BY79rFBDf0idraZWAkYrDjDj8uWaSm3S2TK+hJ7/t1CEmZ7jXw+pg==", + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.0.8.tgz", + "integrity": "sha512-70QWGkr4snxr0OXLRWsFLeRBIRPuQOvt4s8QYjmUlmlkyTZkRqS7EDVRZtzU3TiyDbXSzaOeF0XUKy8PchzukQ==", "dev": true, "license": "MIT", "engines": { "node": ">=18.0.0" } }, - "node_modules/exit": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", - "integrity": "sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==", - "dev": true, - "engines": { - "node": ">= 0.8.0" - } - }, "node_modules/expand-brackets": { "version": "2.1.4", "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", @@ -11543,11 +12027,14 @@ } }, "node_modules/express-rate-limit": { - "version": "7.5.1", - "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-7.5.1.tgz", - "integrity": "sha512-7iN8iPMDzOMHPUYllBEsQdWVB6fPDMPqwjBaFrgr4Jgr/+okjvzAy+UHlYYL/Vs0OsOrMkwS6PJDkFlJwoxUnw==", + "version": "8.5.0", + "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.5.0.tgz", + "integrity": "sha512-XKhFohWaSBdVJNTi5TaHziqnPkv04I9UQV6q1Wy7Ui6GGQZVW12ojDFwqer14EvCXxjvPG0CyWXx7cAXpALB4Q==", "dev": true, "license": "MIT", + "dependencies": { + "ip-address": "10.1.0" + }, "engines": { "node": ">= 16" }, @@ -11692,16 +12179,6 @@ "@types/yauzl": "^2.9.1" } }, - "node_modules/extsprintf": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", - "integrity": "sha512-11Ndz7Nv+mvAC1j0ktTa7fAb0vLyGGX+rMHNBYQviQDGU0Hw7lhctJANqbPhu9nV9/izT/IntTgZ7Im/9LJs9g==", - "dev": true, - "engines": [ - "node >=0.6.0" - ], - "license": "MIT" - }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -11862,7 +12339,6 @@ "version": "7.1.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", - "dev": true, "license": "MIT", "dependencies": { "to-regex-range": "^5.0.1" @@ -11973,16 +12449,16 @@ } }, "node_modules/flatted": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", - "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz", + "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==", "dev": true, "license": "ISC" }, "node_modules/follow-redirects": { - "version": "1.15.11", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz", - "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==", + "version": "1.16.0", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.16.0.tgz", + "integrity": "sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==", "dev": true, "funding": [ { @@ -12024,48 +12500,6 @@ "node": ">=0.10.0" } }, - "node_modules/foreground-child": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", - "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", - "dev": true, - "license": "ISC", - "dependencies": { - "cross-spawn": "^7.0.6", - "signal-exit": "^4.0.1" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/forever-agent": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", - "integrity": "sha512-j0KLYPhm6zeac4lz3oJ3o65qvgQCcPubiyotZrXqEaG4hNagNYO8qdlUrX5vwqv9ohqeT/Z3j6+yW067yWWdUw==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "*" - } - }, - "node_modules/form-data": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", - "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.6", - "mime-types": "^2.1.12" - }, - "engines": { - "node": ">= 0.12" - } - }, "node_modules/forwarded": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", @@ -12337,16 +12771,6 @@ "node": ">=0.10.0" } }, - "node_modules/getpass": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", - "integrity": "sha512-0fzj9JxOLfJ+XGLhR8ze3unN0KZCgZwiSSDz168VERjK8Wl8kVSdcu2kspd4s4wtAa1y/qrVRiAA0WclVsu0ng==", - "dev": true, - "license": "MIT", - "dependencies": { - "assert-plus": "^1.0.0" - } - }, "node_modules/glob": { "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", @@ -12406,9 +12830,9 @@ "license": "BSD-2-Clause" }, "node_modules/glob/node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz", + "integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==", "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", @@ -12416,9 +12840,9 @@ } }, "node_modules/glob/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", "license": "ISC", "dependencies": { "brace-expansion": "^1.1.7" @@ -12472,24 +12896,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/globby": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-5.0.0.tgz", - "integrity": "sha512-HJRTIH2EeH44ka+LWig+EqT2ONSYpVlNfx6pyd592/VF1TbfljJ7elwie7oSwcViLGqOdWocSdu2txwBF9bjmQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "array-union": "^1.0.1", - "arrify": "^1.0.0", - "glob": "^7.0.3", - "object-assign": "^4.0.1", - "pify": "^2.0.0", - "pinkie-promise": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/gopd": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", @@ -12531,78 +12937,6 @@ "dev": true, "license": "MIT" }, - "node_modules/har-schema": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", - "integrity": "sha512-Oqluz6zhGX8cyRaTQlFMPw80bSJVG2x/cFb8ZPhUILGgHka9SsokCCOQgpveePerqidZOrT14ipqfJb7ILcW5Q==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=4" - } - }, - "node_modules/har-validator": { - "version": "5.1.5", - "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.5.tgz", - "integrity": "sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==", - "deprecated": "this library is no longer supported", - "dev": true, - "license": "MIT", - "dependencies": { - "ajv": "^6.12.3", - "har-schema": "^2.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/har-validator/node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/har-validator/node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true, - "license": "MIT" - }, - "node_modules/has-ansi": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", - "integrity": "sha512-C8vBJ8DwUCx19vhm7urhTuUsr4/IyP6l4VzNQDv+ryHQObW3TTTp9yB68WpYgRe2bbaGuZ/se74IqFeVnMnLZg==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/has-ansi/node_modules/ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/has-bigints": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.1.0.tgz", @@ -12814,6 +13148,16 @@ "node": ">=4.0.0" } }, + "node_modules/hono": { + "version": "4.12.17", + "resolved": "https://registry.npmjs.org/hono/-/hono-4.12.17.tgz", + "integrity": "sha512-FbJJNb/XgX7YW0hX/V8w5oYLztKEsRLykCMZWt1WdLtsfjzMvmoqWBA4H4t5norinq8/rh20oiZYr+WSl4UzAQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16.9.0" + } + }, "node_modules/hosted-git-info": { "version": "9.0.2", "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-9.0.2.tgz", @@ -13033,22 +13377,6 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/http-signature": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", - "integrity": "sha512-CAbnr6Rz4CYQkLYUtSNXxQPUH2gK8f3iWexVlsnMeD+GjlsQ0Xsy1cOX+mN3dtxYomRy21CiOzU8Uhw6OwncEQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "assert-plus": "^1.0.0", - "jsprim": "^1.2.2", - "sshpk": "^1.7.0" - }, - "engines": { - "node": ">=0.8", - "npm": ">=1.3.7" - } - }, "node_modules/https-proxy-agent": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", @@ -13151,17 +13479,40 @@ "node": "^20.17.0 || >=22.9.0" } }, + "node_modules/ignore-walk/node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/ignore-walk/node_modules/brace-expansion": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz", + "integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + } + }, "node_modules/ignore-walk/node_modules/minimatch": { - "version": "10.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.1.2.tgz", - "integrity": "sha512-fu656aJ0n2kcXwsnwnv9g24tkU5uSmOlTjd6WyyaKm2Z+h1qmY6bAjrcaIxF/BslFqbZ8UBtbJi7KgQOZD2PTw==", + "version": "10.2.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", + "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", "dev": true, "license": "BlueOak-1.0.0", "dependencies": { - "@isaacs/brace-expansion": "^5.0.1" + "brace-expansion": "^5.0.5" }, "engines": { - "node": "20 || >=22" + "node": "18 || 20 || >=22" }, "funding": { "url": "https://github.com/sponsors/isaacs" @@ -13181,17 +13532,10 @@ "node": ">=0.10.0" } }, - "node_modules/immediate": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", - "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==", - "dev": true, - "license": "MIT" - }, "node_modules/immutable": { - "version": "4.3.7", - "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.7.tgz", - "integrity": "sha512-1hqclzwYwjRDFLjcFxOM5AYkkG0rpFPpr1RLPMEuGczoS7YA8gLhy8SWXYRAA/XwfEHpfo3cw5JGioS32fnMRw==", + "version": "4.3.8", + "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.8.tgz", + "integrity": "sha512-d/Ld9aLbKpNwyl0KiM2CT1WYvkitQ1TSvmRtkcV8FKStiDoA7Slzgjmb/1G2yhKM1p0XeNOieaTbFZmU1d3Xuw==", "dev": true, "license": "MIT" }, @@ -13786,7 +14130,6 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true, "license": "MIT", "engines": { "node": ">=0.12.0" @@ -13808,42 +14151,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-path-cwd": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-1.0.0.tgz", - "integrity": "sha512-cnS56eR9SPAscL77ik76ATVqoPARTqPIVkMDVxRaWH06zT+6+CzIroYRJ0VVvm0Z1zfAvxvz9i/D3Ppjaqt5Nw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-path-in-cwd": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-path-in-cwd/-/is-path-in-cwd-1.0.1.tgz", - "integrity": "sha512-FjV1RTW48E7CWM7eE/J2NJvAEEVektecDBVBE5Hh3nM1Jd0kvhHtX68Pr3xsDf857xt3Y4AkwVULK1Vku62aaQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-path-inside": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-path-in-cwd/node_modules/is-path-inside": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-1.0.1.tgz", - "integrity": "sha512-qhsCR/Esx4U4hg/9I19OVUAJkGWtjRYHMRgUMZE2TDdj+Ag+kttZanLupfddNyglzz50cUlmWzUaI37GDfNx/g==", - "dev": true, - "license": "MIT", - "dependencies": { - "path-is-inside": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/is-path-inside": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", @@ -13977,13 +14284,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-typedarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", - "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==", - "dev": true, - "license": "MIT" - }, "node_modules/is-unicode-supported": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-2.1.0.tgz", @@ -14108,13 +14408,6 @@ "node": ">=0.10.0" } }, - "node_modules/isstream": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", - "integrity": "sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==", - "dev": true, - "license": "MIT" - }, "node_modules/istanbul-lib-coverage": { "version": "3.2.2", "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", @@ -14256,37 +14549,6 @@ "node": ">=8" } }, - "node_modules/jackspeak": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", - "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "@isaacs/cliui": "^8.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - }, - "optionalDependencies": { - "@pkgjs/parseargs": "^0.11.0" - } - }, - "node_modules/jasmine": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/jasmine/-/jasmine-2.8.0.tgz", - "integrity": "sha512-KbdGQTf5jbZgltoHs31XGiChAPumMSY64OZMWLNYnEnMfG5uwGBhffePwuskexjT+/Jea/gU3qAU8344hNohSw==", - "dev": true, - "license": "MIT", - "dependencies": { - "exit": "^0.1.2", - "glob": "^7.0.6", - "jasmine-core": "~2.8.0" - }, - "bin": { - "jasmine": "bin/jasmine.js" - } - }, "node_modules/jasmine-core": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-4.0.0.tgz", @@ -14304,23 +14566,6 @@ "colors": "1.4.0" } }, - "node_modules/jasmine/node_modules/jasmine-core": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-2.8.0.tgz", - "integrity": "sha512-SNkOkS+/jMZvLhuSx1fjhcNWUC/KG6oVyFUGkSBEr9n1axSNduWU8GlI7suaHXr4yxjet6KjrUZxUTE5WzzWwQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/jasminewd2": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/jasminewd2/-/jasminewd2-2.2.0.tgz", - "integrity": "sha512-Rn0nZe4rfDhzA63Al3ZGh0E+JTmM6ESZYXJGKuqKGZObsAB9fwXPD03GjtIEvJBDOhN94T5MzbwZSqzFHSQPzg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 6.9.x" - } - }, "node_modules/jest-worker": { "version": "27.5.1", "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", @@ -14371,6 +14616,16 @@ "jiti": "bin/jiti.js" } }, + "node_modules/jose": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/jose/-/jose-6.2.3.tgz", + "integrity": "sha512-YYVDInQKFJfR/xa3ojUTl8c2KoTwiL1R5Wg9YCydwH0x0B9grbzlg5HC7mMjCtUJjbQ/YnGEZIhI5tCgfTb4Hw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -14391,13 +14646,6 @@ "js-yaml": "bin/js-yaml.js" } }, - "node_modules/jsbn": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", - "integrity": "sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg==", - "dev": true, - "license": "MIT" - }, "node_modules/jsdoc-type-pratt-parser": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/jsdoc-type-pratt-parser/-/jsdoc-type-pratt-parser-7.1.1.tgz", @@ -14429,22 +14677,15 @@ "license": "MIT" }, "node_modules/json-parse-even-better-errors": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-4.0.0.tgz", - "integrity": "sha512-lR4MXjGNgkJc7tkQ97kb2nuEMnNCyU//XYVH0MKTGcXEiSudQ5MKGKen3C5QubYy0vmq+JGitUg92uuywGEwIA==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-5.0.0.tgz", + "integrity": "sha512-ZF1nxZ28VhQouRWhUcVlUIN3qwSgPuswK05s/HIaoetAoE/9tngVmCHjSxmSQPav1nd+lPtTL0YZ/2AFdR/iYQ==", "dev": true, "license": "MIT", "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/json-schema": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz", - "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==", - "dev": true, - "license": "(AFL-2.1 OR BSD-3-Clause)" - }, "node_modules/json-schema-traverse": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", @@ -14452,6 +14693,13 @@ "dev": true, "license": "MIT" }, + "node_modules/json-schema-typed": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/json-schema-typed/-/json-schema-typed-8.0.2.tgz", + "integrity": "sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA==", + "dev": true, + "license": "BSD-2-Clause" + }, "node_modules/json-stable-stringify-without-jsonify": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", @@ -14459,13 +14707,6 @@ "dev": true, "license": "MIT" }, - "node_modules/json-stringify-safe": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", - "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==", - "dev": true, - "license": "ISC" - }, "node_modules/json5": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", @@ -14507,9 +14748,9 @@ "license": "MIT" }, "node_modules/jsonpath": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/jsonpath/-/jsonpath-1.2.1.tgz", - "integrity": "sha512-Jl6Jhk0jG+kP3yk59SSeGq7LFPR4JQz1DU0K+kXTysUhMostbhU3qh5mjTuf0PqFcXpAT7kvmMt9WxV10NyIgQ==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/jsonpath/-/jsonpath-1.3.0.tgz", + "integrity": "sha512-0kjkYHJBkAy50Z5QzArZ7udmvxrJzkpKYW27fiF//BrMY7TQibYLl+FYIXN2BiYmwMIVzSfD8aDRj6IzgBX2/w==", "license": "MIT", "dependencies": { "esprima": "1.2.5", @@ -14544,46 +14785,17 @@ "jspdf": "^2.5.1" } }, - "node_modules/jsprim": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.2.tgz", - "integrity": "sha512-P2bSOMAc/ciLz6DzgjVlGJP9+BrJWu5UDGK70C2iweC5QBIeFf0ZXRvGjEj2uYgrY2MkAAhsSWHDWlFtEroZWw==", - "dev": true, - "license": "MIT", - "dependencies": { - "assert-plus": "1.0.0", - "extsprintf": "1.3.0", - "json-schema": "0.4.0", - "verror": "1.10.0" - }, - "engines": { - "node": ">=0.6.0" - } - }, - "node_modules/jszip": { - "version": "3.10.1", - "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.1.tgz", - "integrity": "sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==", - "dev": true, - "license": "(MIT OR GPL-3.0-or-later)", - "dependencies": { - "lie": "~3.3.0", - "pako": "~1.0.2", - "readable-stream": "~2.3.6", - "setimmediate": "^1.0.5" - } - }, "node_modules/karma": { - "version": "6.3.15", - "resolved": "https://registry.npmjs.org/karma/-/karma-6.3.15.tgz", - "integrity": "sha512-4O5X6zVFdmwo/fgjRN84fPG3IvaiOxOjIeZBwBrQYz4nIyGqlF8Wm7C1Hr7idQ9NHgnvJM+LSjZwS1C+qALMGw==", + "version": "6.4.4", + "resolved": "https://registry.npmjs.org/karma/-/karma-6.4.4.tgz", + "integrity": "sha512-LrtUxbdvt1gOpo3gxG+VAJlJAEMhbWlM4YrFQgql98FwF7+K8K12LYO4hnDdUkNjeztYrOXEMqgTajSWgmtI/w==", "dev": true, "license": "MIT", "dependencies": { + "@colors/colors": "1.5.0", "body-parser": "^1.19.0", "braces": "^3.0.2", "chokidar": "^3.5.1", - "colors": "1.4.0", "connect": "^3.7.0", "di": "^0.0.1", "dom-serialize": "^2.2.1", @@ -14599,7 +14811,7 @@ "qjobs": "^1.2.0", "range-parser": "^1.2.1", "rimraf": "^3.0.2", - "socket.io": "^4.2.0", + "socket.io": "^4.7.2", "source-map": "^0.6.1", "tmp": "^0.2.1", "ua-parser-js": "^0.7.30", @@ -14653,9 +14865,9 @@ } }, "node_modules/karma-coverage-istanbul-reporter/node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz", + "integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==", "dev": true, "license": "MIT", "dependencies": { @@ -14664,9 +14876,9 @@ } }, "node_modules/karma-coverage-istanbul-reporter/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", "dev": true, "license": "ISC", "dependencies": { @@ -14747,9 +14959,9 @@ } }, "node_modules/karma/node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz", + "integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==", "dev": true, "license": "MIT", "dependencies": { @@ -14757,16 +14969,6 @@ "concat-map": "0.0.1" } }, - "node_modules/karma/node_modules/bytes": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", - "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, "node_modules/karma/node_modules/chokidar": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", @@ -14868,9 +15070,9 @@ } }, "node_modules/karma/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", "dev": true, "license": "ISC", "dependencies": { @@ -14901,9 +15103,9 @@ "license": "MIT" }, "node_modules/karma/node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", "dev": true, "license": "MIT", "engines": { @@ -15200,20 +15402,10 @@ "peerDependenciesMeta": { "webpack": { "optional": true - }, - "webpack-sources": { - "optional": true - } - } - }, - "node_modules/lie": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz", - "integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "immediate": "~3.0.5" + }, + "webpack-sources": { + "optional": true + } } }, "node_modules/lines-and-columns": { @@ -15377,9 +15569,9 @@ } }, "node_modules/lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz", + "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==", "license": "MIT" }, "node_modules/lodash.debounce": { @@ -15633,26 +15825,27 @@ "license": "ISC" }, "node_modules/make-fetch-happen": { - "version": "14.0.3", - "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-14.0.3.tgz", - "integrity": "sha512-QMjGbFTP0blj97EeidG5hk/QhKQ3T4ICckQGLgz38QF7Vgbk6e6FTARN8KhKxyBbWn8R0HU+bnw8aSoFPD4qtQ==", + "version": "15.0.5", + "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-15.0.5.tgz", + "integrity": "sha512-uCbIa8jWWmQZt4dSnEStkVC6gdakiinAm4PiGsywIkguF0eWMdcjDz0ECYhUolFU3pFLOev9VNPCEygydXnddg==", "dev": true, "license": "ISC", "dependencies": { - "@npmcli/agent": "^3.0.0", - "cacache": "^19.0.1", + "@gar/promise-retry": "^1.0.0", + "@npmcli/agent": "^4.0.0", + "@npmcli/redact": "^4.0.0", + "cacache": "^20.0.1", "http-cache-semantics": "^4.1.1", "minipass": "^7.0.2", - "minipass-fetch": "^4.0.0", + "minipass-fetch": "^5.0.0", "minipass-flush": "^1.0.5", "minipass-pipeline": "^1.2.4", "negotiator": "^1.0.0", - "proc-log": "^5.0.0", - "promise-retry": "^2.0.1", - "ssri": "^12.0.0" + "proc-log": "^6.0.0", + "ssri": "^13.0.0" }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/make-fetch-happen/node_modules/negotiator": { @@ -15665,6 +15858,16 @@ "node": ">= 0.6" } }, + "node_modules/make-fetch-happen/node_modules/proc-log": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-6.1.0.tgz", + "integrity": "sha512-iG+GYldRf2BQ0UDUAd6JQ/RwzaQy6mXmsk/IzlYyal4A4SNFw54MeH4/tLkF4I5WoWG9SQwuqWzS99jaFQHBuQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, "node_modules/map-cache": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz", @@ -15803,9 +16006,9 @@ } }, "node_modules/micromatch/node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", "dev": true, "license": "MIT", "engines": { @@ -15840,6 +16043,7 @@ "version": "2.1.35", "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dev": true, "license": "MIT", "dependencies": { "mime-db": "1.52.0" @@ -15852,6 +16056,7 @@ "version": "1.52.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "dev": true, "license": "MIT", "engines": { "node": ">= 0.6" @@ -15899,13 +16104,13 @@ "license": "ISC" }, "node_modules/minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", "dev": true, "license": "ISC", "dependencies": { - "brace-expansion": "^2.0.1" + "brace-expansion": "^2.0.2" }, "engines": { "node": ">=16 || 14 >=14.17" @@ -15924,11 +16129,11 @@ } }, "node_modules/minipass": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", - "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", + "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", "dev": true, - "license": "ISC", + "license": "BlueOak-1.0.0", "engines": { "node": ">=16 || 14 >=14.17" } @@ -15947,29 +16152,29 @@ } }, "node_modules/minipass-fetch": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-4.0.1.tgz", - "integrity": "sha512-j7U11C5HXigVuutxebFadoYBbd7VSdZWggSe64NVdvWNBqGAiXPL2QVCehjmw7lY1oF9gOllYbORh+hiNgfPgQ==", + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-5.0.2.tgz", + "integrity": "sha512-2d0q2a8eCi2IRg/IGubCNRJoYbA1+YPXAzQVRFmB45gdGZafyivnZ5YSEfo3JikbjGxOdntGFvBQGqaSMXlAFQ==", "dev": true, "license": "MIT", "dependencies": { "minipass": "^7.0.3", - "minipass-sized": "^1.0.3", + "minipass-sized": "^2.0.0", "minizlib": "^3.0.1" }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": "^20.17.0 || >=22.9.0" }, "optionalDependencies": { - "encoding": "^0.1.13" + "iconv-lite": "^0.7.2" } }, "node_modules/minipass-flush": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/minipass-flush/-/minipass-flush-1.0.5.tgz", - "integrity": "sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw==", + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/minipass-flush/-/minipass-flush-1.0.7.tgz", + "integrity": "sha512-TbqTz9cUwWyHS2Dy89P3ocAGUGxKjjLuR9z8w4WUTGAVgEj17/4nhgo2Du56i0Fm3Pm30g4iA8Lcqctc76jCzA==", "dev": true, - "license": "ISC", + "license": "BlueOak-1.0.0", "dependencies": { "minipass": "^3.0.0" }, @@ -16031,38 +16236,18 @@ "license": "ISC" }, "node_modules/minipass-sized": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/minipass-sized/-/minipass-sized-1.0.3.tgz", - "integrity": "sha512-MbkQQ2CTiBMlA2Dm/5cY+9SWFEN8pzzOXi6rlM5Xxq0Yqbda5ZQy9sU75a673FE9ZK0Zsbr6Y5iP6u9nktfg2g==", - "dev": true, - "license": "ISC", - "dependencies": { - "minipass": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/minipass-sized/node_modules/minipass": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/minipass-sized/-/minipass-sized-2.0.0.tgz", + "integrity": "sha512-zSsHhto5BcUVM2m1LurnXY6M//cGhVaegT71OfOXoprxT6o780GZd792ea6FfrQkuU4usHZIUczAQMRUE2plzA==", "dev": true, "license": "ISC", "dependencies": { - "yallist": "^4.0.0" + "minipass": "^7.1.2" }, "engines": { "node": ">=8" } }, - "node_modules/minipass-sized/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true, - "license": "ISC" - }, "node_modules/minizlib": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.1.0.tgz", @@ -16128,9 +16313,9 @@ "license": "MIT" }, "node_modules/msgpackr": { - "version": "1.11.8", - "resolved": "https://registry.npmjs.org/msgpackr/-/msgpackr-1.11.8.tgz", - "integrity": "sha512-bC4UGzHhVvgDNS7kn9tV8fAucIYUBuGojcaLiz7v+P63Lmtm0Xeji8B/8tYKddALXxJLpwIeBmUN3u64C4YkRA==", + "version": "1.11.12", + "resolved": "https://registry.npmjs.org/msgpackr/-/msgpackr-1.11.12.tgz", + "integrity": "sha512-RBdJ1Un7yGlXWajrkxcSa93nvQ0w4zBf60c0yYv7YtBelP8H2FA7XsfBbMHtXKXUMUxH7zV3Zuozh+kUQWhHvg==", "dev": true, "license": "MIT", "optional": true, @@ -16269,6 +16454,7 @@ "version": "0.6.3", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "dev": true, "license": "MIT", "engines": { "node": ">= 0.6" @@ -16341,9 +16527,9 @@ } }, "node_modules/ng-packagr/node_modules/immutable": { - "version": "5.1.4", - "resolved": "https://registry.npmjs.org/immutable/-/immutable-5.1.4.tgz", - "integrity": "sha512-p6u1bG3YSnINT5RQmx/yRZBpenIl30kVxkTLDyHLIMk0gict704Q9n+thfDI7lTRm9vXdDYutVzXhzcThxTnXA==", + "version": "5.1.5", + "resolved": "https://registry.npmjs.org/immutable/-/immutable-5.1.5.tgz", + "integrity": "sha512-t7xcm2siw+hlUM68I+UEOK+z84RzmN59as9DZ7P1l0994DKUWV7UXBMQZVxaoMSRQ+PBZbHCOoBt7a2wxOMt+A==", "dev": true, "license": "MIT" }, @@ -16427,9 +16613,9 @@ "optional": true }, "node_modules/node-forge": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.3.tgz", - "integrity": "sha512-rLvcdSyRCyouf6jcOIPe/BgwG/d7hKjzMKOas33/pHEr6gbq18IK9zV7DiPvzsz0oBJPme6qr6H6kGZuI9/DZg==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.4.0.tgz", + "integrity": "sha512-LarFH0+6VfriEhqMMcLX2F7SwSXeWwnEAJEsYm5QKWchiVYVvJyV9v7UDvUv+w5HO23ZpQTXDv/GxdDdMyOuoQ==", "dev": true, "license": "(BSD-3-Clause OR GPL-2.0)", "engines": { @@ -16437,28 +16623,28 @@ } }, "node_modules/node-gyp": { - "version": "11.5.0", - "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-11.5.0.tgz", - "integrity": "sha512-ra7Kvlhxn5V9Slyus0ygMa2h+UqExPqUIkfk7Pc8QTLT956JLSy51uWFwHtIYy0vI8cB4BDhc/S03+880My/LQ==", + "version": "12.3.0", + "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-12.3.0.tgz", + "integrity": "sha512-QNcUWM+HgJplcPzBvFBZ9VXacyGZ4+VTOb80PwWR+TlVzoHbRKULNEzpRsnaoxG3Wzr7Qh7BYxGDU3CbKib2Yg==", "dev": true, "license": "MIT", "dependencies": { "env-paths": "^2.2.0", "exponential-backoff": "^3.1.1", "graceful-fs": "^4.2.6", - "make-fetch-happen": "^14.0.3", - "nopt": "^8.0.0", - "proc-log": "^5.0.0", + "nopt": "^9.0.0", + "proc-log": "^6.0.0", "semver": "^7.3.5", - "tar": "^7.4.3", + "tar": "^7.5.4", "tinyglobby": "^0.2.12", - "which": "^5.0.0" + "undici": "^6.25.0", + "which": "^6.0.0" }, "bin": { "node-gyp": "bin/node-gyp.js" }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/node-gyp-build-optional-packages": { @@ -16477,67 +16663,40 @@ "node-gyp-build-optional-packages-test": "build-test.js" } }, - "node_modules/node-gyp/node_modules/chownr": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz", - "integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==", - "dev": true, - "license": "BlueOak-1.0.0", - "engines": { - "node": ">=18" - } - }, "node_modules/node-gyp/node_modules/isexe": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.5.tgz", - "integrity": "sha512-6B3tLtFqtQS4ekarvLVMZ+X+VlvQekbe4taUkf/rhVO3d/h0M2rfARm/pXLcPEsjjMsFgrFgSrhQIxcSVrBz8w==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-4.0.0.tgz", + "integrity": "sha512-FFUtZMpoZ8RqHS3XeXEmHWLA4thH+ZxCv2lOiPIn1Xc7CxrqhWzNSDzD+/chS/zbYezmiwWLdQC09JdQKmthOw==", "dev": true, "license": "BlueOak-1.0.0", "engines": { - "node": ">=18" + "node": ">=20" } }, - "node_modules/node-gyp/node_modules/tar": { - "version": "7.5.7", - "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.7.tgz", - "integrity": "sha512-fov56fJiRuThVFXD6o6/Q354S7pnWMJIVlDBYijsTNx6jKSE4pvrDTs6lUnmGvNyfJwFQQwWy3owKz1ucIhveQ==", + "node_modules/node-gyp/node_modules/proc-log": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-6.1.0.tgz", + "integrity": "sha512-iG+GYldRf2BQ0UDUAd6JQ/RwzaQy6mXmsk/IzlYyal4A4SNFw54MeH4/tLkF4I5WoWG9SQwuqWzS99jaFQHBuQ==", "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "@isaacs/fs-minipass": "^4.0.0", - "chownr": "^3.0.0", - "minipass": "^7.1.2", - "minizlib": "^3.1.0", - "yallist": "^5.0.0" - }, + "license": "ISC", "engines": { - "node": ">=18" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/node-gyp/node_modules/which": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/which/-/which-5.0.0.tgz", - "integrity": "sha512-JEdGzHwwkrbWoGOlIHqQ5gtprKGOenpDHpxE9zVR1bWbOtYRyPPHMe9FaP6x61CmNaTThSkb0DAJte5jD+DmzQ==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/which/-/which-6.0.1.tgz", + "integrity": "sha512-oGLe46MIrCRqX7ytPUf66EAYvdeMIZYn3WaocqqKZAxrBpkqHfL/qvTyJ/bTk5+AqHCjXmrv3CEWgy368zhRUg==", "dev": true, "license": "ISC", "dependencies": { - "isexe": "^3.1.1" + "isexe": "^4.0.0" }, "bin": { "node-which": "bin/which.js" }, "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/node-gyp/node_modules/yallist": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz", - "integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==", - "dev": true, - "license": "BlueOak-1.0.0", - "engines": { - "node": ">=18" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/node-releases": { @@ -16548,19 +16707,19 @@ "license": "MIT" }, "node_modules/nopt": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-8.1.0.tgz", - "integrity": "sha512-ieGu42u/Qsa4TFktmaKEwM6MQH0pOWnaB3htzh0JRtx84+Mebc0cbZYN5bC+6WTZ4+77xrL9Pn5m7CV6VIkV7A==", + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-9.0.0.tgz", + "integrity": "sha512-Zhq3a+yFKrYwSBluL4H9XP3m3y5uvQkB/09CwDruCiRmR/UJYnn9W4R48ry0uGC70aeTPKLynBtscP9efFFcPw==", "dev": true, "license": "ISC", "dependencies": { - "abbrev": "^3.0.0" + "abbrev": "^4.0.0" }, "bin": { "nopt": "bin/nopt.js" }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/normalize-path": { @@ -16584,39 +16743,39 @@ } }, "node_modules/npm-bundled": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-4.0.0.tgz", - "integrity": "sha512-IxaQZDMsqfQ2Lz37VvyyEtKLe8FsRZuysmedy/N06TU1RyVppYKXrO4xIhR0F+7ubIBox6Q7nir6fQI3ej39iA==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-5.0.0.tgz", + "integrity": "sha512-JLSpbzh6UUXIEoqPsYBvVNVmyrjVZ1fzEFbqxKkTJQkWBO3xFzFT+KDnSKQWwOQNbuWRwt5LSD6HOTLGIWzfrw==", "dev": true, "license": "ISC", "dependencies": { - "npm-normalize-package-bin": "^4.0.0" + "npm-normalize-package-bin": "^5.0.0" }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm-install-checks": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/npm-install-checks/-/npm-install-checks-7.1.2.tgz", - "integrity": "sha512-z9HJBCYw9Zr8BqXcllKIs5nI+QggAImbBdHphOzVYrz2CB4iQ6FzWyKmlqDZua+51nAu7FcemlbTc9VgQN5XDQ==", + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/npm-install-checks/-/npm-install-checks-8.0.0.tgz", + "integrity": "sha512-ScAUdMpyzkbpxoNekQ3tNRdFI8SJ86wgKZSQZdUxT+bj0wVFpsEMWnkXP0twVe1gJyNF5apBWDJhhIbgrIViRA==", "dev": true, "license": "BSD-2-Clause", "dependencies": { "semver": "^7.1.1" }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm-normalize-package-bin": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-4.0.0.tgz", - "integrity": "sha512-TZKxPvItzai9kN9H/TkmCtx/ZN/hvr3vUycjlfmH0ootY9yFBzNOpiXAdIn1Iteqsvk4lQn6B5PTrt+n6h8k/w==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-5.0.0.tgz", + "integrity": "sha512-CJi3OS4JLsNMmr2u07OJlhcrPxCeOeP/4xq67aWNai6TNWWbTrlNDgl8NcFKVlcBKp18GPj+EzbNIgrBfZhsag==", "dev": true, "license": "ISC", "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm-package-arg": { @@ -16636,9 +16795,9 @@ } }, "node_modules/npm-packlist": { - "version": "10.0.3", - "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-10.0.3.tgz", - "integrity": "sha512-zPukTwJMOu5X5uvm0fztwS5Zxyvmk38H/LfidkOMt3gbZVCyro2cD/ETzwzVPcWZA3JOyPznfUN/nkyFiyUbxg==", + "version": "10.0.4", + "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-10.0.4.tgz", + "integrity": "sha512-uMW73iajD8hiH4ZBxEV3HC+eTnppIqwakjOYuvgddnalIw2lJguKviK1pcUJDlIWm1wSJkchpDZDSVVsZEYRng==", "dev": true, "license": "ISC", "dependencies": { @@ -16660,111 +16819,49 @@ } }, "node_modules/npm-pick-manifest": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/npm-pick-manifest/-/npm-pick-manifest-10.0.0.tgz", - "integrity": "sha512-r4fFa4FqYY8xaM7fHecQ9Z2nE9hgNfJR+EmoKv0+chvzWkBcORX3r0FpTByP+CbOVJDladMXnPQGVN8PBLGuTQ==", + "version": "11.0.3", + "resolved": "https://registry.npmjs.org/npm-pick-manifest/-/npm-pick-manifest-11.0.3.tgz", + "integrity": "sha512-buzyCfeoGY/PxKqmBqn1IUJrZnUi1VVJTdSSRPGI60tJdUhUoSQFhs0zycJokDdOznQentgrpf8LayEHyyYlqQ==", "dev": true, "license": "ISC", "dependencies": { - "npm-install-checks": "^7.1.0", - "npm-normalize-package-bin": "^4.0.0", - "npm-package-arg": "^12.0.0", + "npm-install-checks": "^8.0.0", + "npm-normalize-package-bin": "^5.0.0", + "npm-package-arg": "^13.0.0", "semver": "^7.3.5" }, "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/npm-pick-manifest/node_modules/hosted-git-info": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-8.1.0.tgz", - "integrity": "sha512-Rw/B2DNQaPBICNXEm8balFz9a6WpZrkCGpcWFpy7nCj+NyhSdqXipmfvtmWt9xGfp0wZnBxB+iVpLmQMYt47Tw==", - "dev": true, - "license": "ISC", - "dependencies": { - "lru-cache": "^10.0.1" - }, - "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/npm-pick-manifest/node_modules/lru-cache": { - "version": "10.4.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", - "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/npm-pick-manifest/node_modules/npm-package-arg": { - "version": "12.0.2", - "resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-12.0.2.tgz", - "integrity": "sha512-f1NpFjNI9O4VbKMOlA5QoBq/vSQPORHcTZ2feJpFkTHJ9eQkdlmZEKSjcAhxTGInC7RlEyScT9ui67NaOsjFWA==", - "dev": true, - "license": "ISC", - "dependencies": { - "hosted-git-info": "^8.0.0", - "proc-log": "^5.0.0", - "semver": "^7.3.5", - "validate-npm-package-name": "^6.0.0" - }, - "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm-registry-fetch": { - "version": "18.0.2", - "resolved": "https://registry.npmjs.org/npm-registry-fetch/-/npm-registry-fetch-18.0.2.tgz", - "integrity": "sha512-LeVMZBBVy+oQb5R6FDV9OlJCcWDU+al10oKpe+nsvcHnG24Z3uM3SvJYKfGJlfGjVU8v9liejCrUR/M5HO5NEQ==", + "version": "19.1.1", + "resolved": "https://registry.npmjs.org/npm-registry-fetch/-/npm-registry-fetch-19.1.1.tgz", + "integrity": "sha512-TakBap6OM1w0H73VZVDf44iFXsOS3h+L4wVMXmbWOQroZgFhMch0juN6XSzBNlD965yIKvWg2dfu7NSiaYLxtw==", "dev": true, "license": "ISC", "dependencies": { - "@npmcli/redact": "^3.0.0", + "@npmcli/redact": "^4.0.0", "jsonparse": "^1.3.1", - "make-fetch-happen": "^14.0.0", + "make-fetch-happen": "^15.0.0", "minipass": "^7.0.2", - "minipass-fetch": "^4.0.0", + "minipass-fetch": "^5.0.0", "minizlib": "^3.0.1", - "npm-package-arg": "^12.0.0", - "proc-log": "^5.0.0" - }, - "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/npm-registry-fetch/node_modules/hosted-git-info": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-8.1.0.tgz", - "integrity": "sha512-Rw/B2DNQaPBICNXEm8balFz9a6WpZrkCGpcWFpy7nCj+NyhSdqXipmfvtmWt9xGfp0wZnBxB+iVpLmQMYt47Tw==", - "dev": true, - "license": "ISC", - "dependencies": { - "lru-cache": "^10.0.1" + "npm-package-arg": "^13.0.0", + "proc-log": "^6.0.0" }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/npm-registry-fetch/node_modules/lru-cache": { - "version": "10.4.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", - "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/npm-registry-fetch/node_modules/npm-package-arg": { - "version": "12.0.2", - "resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-12.0.2.tgz", - "integrity": "sha512-f1NpFjNI9O4VbKMOlA5QoBq/vSQPORHcTZ2feJpFkTHJ9eQkdlmZEKSjcAhxTGInC7RlEyScT9ui67NaOsjFWA==", + "node_modules/npm-registry-fetch/node_modules/proc-log": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-6.1.0.tgz", + "integrity": "sha512-iG+GYldRf2BQ0UDUAd6JQ/RwzaQy6mXmsk/IzlYyal4A4SNFw54MeH4/tLkF4I5WoWG9SQwuqWzS99jaFQHBuQ==", "dev": true, "license": "ISC", - "dependencies": { - "hosted-git-info": "^8.0.0", - "proc-log": "^5.0.0", - "semver": "^7.3.5", - "validate-npm-package-name": "^6.0.0" - }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/nth-check": { @@ -16780,16 +16877,6 @@ "url": "https://github.com/fb55/nth-check?sponsor=1" } }, - "node_modules/oauth-sign": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", - "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "*" - } - }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -17013,9 +17100,9 @@ } }, "node_modules/on-headers": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", - "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.1.0.tgz", + "integrity": "sha512-737ZY3yNnXy37FHkQxPzt4UZ2UWPWiCZWLvFZ4fu5cueciegX0zGPnrlY6bwRg4FdQOe9YU8MkmJwGhoMybl8A==", "license": "MIT", "engines": { "node": ">= 0.8" @@ -17157,16 +17244,6 @@ "license": "MIT", "optional": true }, - "node_modules/os-tmpdir": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", - "integrity": "sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/own-keys": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/own-keys/-/own-keys-1.0.1.tgz", @@ -17268,37 +17345,30 @@ "node": ">=6" } }, - "node_modules/package-json-from-dist": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", - "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", - "dev": true, - "license": "BlueOak-1.0.0" - }, "node_modules/pacote": { - "version": "21.0.0", - "resolved": "https://registry.npmjs.org/pacote/-/pacote-21.0.0.tgz", - "integrity": "sha512-lcqexq73AMv6QNLo7SOpz0JJoaGdS3rBFgF122NZVl1bApo2mfu+XzUBU/X/XsiJu+iUmKpekRayqQYAs+PhkA==", + "version": "21.0.4", + "resolved": "https://registry.npmjs.org/pacote/-/pacote-21.0.4.tgz", + "integrity": "sha512-RplP/pDW0NNNDh3pnaoIWYPvNenS7UqMbXyvMqJczosiFWTeGGwJC2NQBLqKf4rGLFfwCOnntw1aEp9Jiqm1MA==", "dev": true, "license": "ISC", "dependencies": { - "@npmcli/git": "^6.0.0", - "@npmcli/installed-package-contents": "^3.0.0", - "@npmcli/package-json": "^6.0.0", - "@npmcli/promise-spawn": "^8.0.0", - "@npmcli/run-script": "^9.0.0", - "cacache": "^19.0.0", + "@npmcli/git": "^7.0.0", + "@npmcli/installed-package-contents": "^4.0.0", + "@npmcli/package-json": "^7.0.0", + "@npmcli/promise-spawn": "^9.0.0", + "@npmcli/run-script": "^10.0.0", + "cacache": "^20.0.0", "fs-minipass": "^3.0.0", "minipass": "^7.0.2", - "npm-package-arg": "^12.0.0", - "npm-packlist": "^10.0.0", - "npm-pick-manifest": "^10.0.0", - "npm-registry-fetch": "^18.0.0", - "proc-log": "^5.0.0", + "npm-package-arg": "^13.0.0", + "npm-packlist": "^10.0.1", + "npm-pick-manifest": "^11.0.1", + "npm-registry-fetch": "^19.0.0", + "proc-log": "^6.0.0", "promise-retry": "^2.0.1", - "sigstore": "^3.0.0", - "ssri": "^12.0.0", - "tar": "^6.1.11" + "sigstore": "^4.0.0", + "ssri": "^13.0.0", + "tar": "^7.4.3" }, "bin": { "pacote": "bin/index.js" @@ -17307,47 +17377,20 @@ "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/pacote/node_modules/hosted-git-info": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-8.1.0.tgz", - "integrity": "sha512-Rw/B2DNQaPBICNXEm8balFz9a6WpZrkCGpcWFpy7nCj+NyhSdqXipmfvtmWt9xGfp0wZnBxB+iVpLmQMYt47Tw==", - "dev": true, - "license": "ISC", - "dependencies": { - "lru-cache": "^10.0.1" - }, - "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/pacote/node_modules/lru-cache": { - "version": "10.4.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", - "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/pacote/node_modules/npm-package-arg": { - "version": "12.0.2", - "resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-12.0.2.tgz", - "integrity": "sha512-f1NpFjNI9O4VbKMOlA5QoBq/vSQPORHcTZ2feJpFkTHJ9eQkdlmZEKSjcAhxTGInC7RlEyScT9ui67NaOsjFWA==", + "node_modules/pacote/node_modules/proc-log": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-6.1.0.tgz", + "integrity": "sha512-iG+GYldRf2BQ0UDUAd6JQ/RwzaQy6mXmsk/IzlYyal4A4SNFw54MeH4/tLkF4I5WoWG9SQwuqWzS99jaFQHBuQ==", "dev": true, "license": "ISC", - "dependencies": { - "hosted-git-info": "^8.0.0", - "proc-log": "^5.0.0", - "semver": "^7.3.5", - "validate-npm-package-name": "^6.0.0" - }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/pako": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", - "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", - "dev": true, + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/pako/-/pako-2.1.0.tgz", + "integrity": "sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug==", "license": "(MIT AND Zlib)" }, "node_modules/parent-module": { @@ -17519,13 +17562,6 @@ "node": ">=0.10.0" } }, - "node_modules/path-is-inside": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz", - "integrity": "sha512-DUWJr3+ULp4zXmol/SZkFf3JGsS9/SIv+Y3Rt93/UjPpDpklB5f1er4O3POIbUuUJ3FXgqte2Q7SrU6zAqwk8w==", - "dev": true, - "license": "(WTFPL OR MIT)" - }, "node_modules/path-key": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", @@ -17544,33 +17580,36 @@ "license": "MIT" }, "node_modules/path-scurry": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", - "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.2.tgz", + "integrity": "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==", "dev": true, "license": "BlueOak-1.0.0", "dependencies": { - "lru-cache": "^10.2.0", - "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + "lru-cache": "^11.0.0", + "minipass": "^7.1.2" }, "engines": { - "node": ">=16 || 14 >=14.18" + "node": "18 || 20 || >=22" }, "funding": { "url": "https://github.com/sponsors/isaacs" } }, "node_modules/path-scurry/node_modules/lru-cache": { - "version": "10.4.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", - "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "version": "11.3.6", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.3.6.tgz", + "integrity": "sha512-Gf/KoL3C/MlI7Bt0PGI9I+TeTC/I6r/csU58N4BSNc4lppLBeKsOdFYkK+dX0ABDUMJNfCHTyPpzwwO21Awd3A==", "dev": true, - "license": "ISC" + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } }, "node_modules/path-to-regexp": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.3.0.tgz", - "integrity": "sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==", + "version": "8.4.2", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.4.2.tgz", + "integrity": "sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==", "dev": true, "license": "MIT", "funding": { @@ -17588,60 +17627,27 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", "integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==", - "devOptional": true, - "license": "MIT" + "license": "MIT", + "optional": true }, "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", - "dev": true, - "license": "ISC" - }, - "node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/pify": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/pinkie": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", - "integrity": "sha512-MnUuEycAemtSaeFSjXKW/aroV7akBbY+Sv+RkyqFjgAe73F+MR0TBWKBRDkmfWq/HiFmdavfZ1G7h4SPZXaCSg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } + "dev": true, + "license": "ISC" }, - "node_modules/pinkie-promise": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", - "integrity": "sha512-0Gni6D4UcLTbv9c57DfxDGdr41XfgUjqWZu492f0cIGr16zDU06BWP/RAEvOuo7CQ0CNjHaLlM59YJJFm3NWlw==", + "node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "dev": true, "license": "MIT", - "dependencies": { - "pinkie": "^2.0.0" - }, "engines": { - "node": ">=0.10.0" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" } }, "node_modules/piscina": { @@ -17702,9 +17708,9 @@ } }, "node_modules/postcss": { - "version": "8.5.6", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", - "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "version": "8.5.12", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.12.tgz", + "integrity": "sha512-W62t/Se6rA0Az3DfCL0AqJwXuKwBeYg6nOaIgzP+xZ7N5BFCI7DYi1qs6ygUYT6rvfi6t9k65UMLJC+PHZpDAA==", "dev": true, "funding": [ { @@ -17873,367 +17879,55 @@ "@primeuix/styles": "^1.2.5", "@primeuix/utils": "^0.6.2", "tslib": "^2.3.0" - }, - "peerDependencies": { - "@angular/animations": "^20.0.4", - "@angular/cdk": "^20.0.3", - "@angular/common": "^20.0.4", - "@angular/core": "^20.0.4", - "@angular/forms": "^20.0.4", - "@angular/platform-browser": "^20.0.4", - "@angular/router": "^20.0.4", - "rxjs": "^6.0.0 || ^7.8.1" - } - }, - "node_modules/proc-log": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-5.0.0.tgz", - "integrity": "sha512-Azwzvl90HaF0aCz1JrDdXQykFakSSNPaPoiZ9fm5qJIMHioDZEi7OAdRwSm6rSoPtY3Qutnm3L7ogmg3dc+wbQ==", - "dev": true, - "license": "ISC", - "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/process-nextick-args": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", - "license": "MIT" - }, - "node_modules/progress": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", - "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", - "license": "MIT", - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/promise-retry": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/promise-retry/-/promise-retry-2.0.1.tgz", - "integrity": "sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==", - "dev": true, - "license": "MIT", - "dependencies": { - "err-code": "^2.0.2", - "retry": "^0.12.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/protractor": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/protractor/-/protractor-7.0.0.tgz", - "integrity": "sha512-UqkFjivi4GcvUQYzqGYNe0mLzfn5jiLmO8w9nMhQoJRLhy2grJonpga2IWhI6yJO30LibWXJJtA4MOIZD2GgZw==", - "deprecated": "We have news to share - Protractor is deprecated and will reach end-of-life by Summer 2023. To learn more and find out about other options please refer to this post on the Angular blog. Thank you for using and contributing to Protractor. https://goo.gle/state-of-e2e-in-angular", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/q": "^0.0.32", - "@types/selenium-webdriver": "^3.0.0", - "blocking-proxy": "^1.0.0", - "browserstack": "^1.5.1", - "chalk": "^1.1.3", - "glob": "^7.0.3", - "jasmine": "2.8.0", - "jasminewd2": "^2.1.0", - "q": "1.4.1", - "saucelabs": "^1.5.0", - "selenium-webdriver": "3.6.0", - "source-map-support": "~0.4.0", - "webdriver-js-extender": "2.1.0", - "webdriver-manager": "^12.1.7", - "yargs": "^15.3.1" - }, - "bin": { - "protractor": "bin/protractor", - "webdriver-manager": "bin/webdriver-manager" - }, - "engines": { - "node": ">=10.13.x" - } - }, - "node_modules/protractor/node_modules/ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/protractor/node_modules/ansi-styles": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", - "integrity": "sha512-kmCevFghRiWM7HB5zTPULl4r9bVFSWjz62MhqizDGUrq2NWuNMQyuv4tHHoKJHs69M/MF64lEcHdYIocrdWQYA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/protractor/node_modules/chalk": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha512-U3lRVLMSlsCfjqYPbLyVv11M9CPW4I728d6TCKMAOJueEeB9/8o+eSsMnxPJD+Q+K909sdESg7C+tIkoH6on1A==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^2.2.1", - "escape-string-regexp": "^1.0.2", - "has-ansi": "^2.0.0", - "strip-ansi": "^3.0.0", - "supports-color": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/protractor/node_modules/cliui": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", - "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", - "dev": true, - "license": "ISC", - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^6.2.0" - } - }, - "node_modules/protractor/node_modules/cliui/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/protractor/node_modules/cliui/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/protractor/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true, - "license": "MIT" - }, - "node_modules/protractor/node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/protractor/node_modules/find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, - "license": "MIT", - "dependencies": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/protractor/node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/protractor/node_modules/locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-locate": "^4.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/protractor/node_modules/p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-try": "^2.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/protractor/node_modules/p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-limit": "^2.2.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/protractor/node_modules/source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/protractor/node_modules/source-map-support": { - "version": "0.4.18", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.4.18.tgz", - "integrity": "sha512-try0/JqxPLF9nOjvSta7tVondkP5dwgyLDjVoyMDlmjugT2lRZ1OfsrYTkCd2hkDnJTKRbO/Rl3orm8vlsUzbA==", - "dev": true, - "license": "MIT", - "dependencies": { - "source-map": "^0.5.6" - } - }, - "node_modules/protractor/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/protractor/node_modules/string-width/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/protractor/node_modules/string-width/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/protractor/node_modules/strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" + }, + "peerDependencies": { + "@angular/animations": "^20.0.4", + "@angular/cdk": "^20.0.3", + "@angular/common": "^20.0.4", + "@angular/core": "^20.0.4", + "@angular/forms": "^20.0.4", + "@angular/platform-browser": "^20.0.4", + "@angular/router": "^20.0.4", + "rxjs": "^6.0.0 || ^7.8.1" } }, - "node_modules/protractor/node_modules/supports-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha512-KKNVtd6pCYgPIKU4cp2733HWYCpplQhddZLBUryaAHou723x+FRzQ5Df824Fj+IyyuiQTRoub4SnIFfIcrp70g==", + "node_modules/proc-log": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-5.0.0.tgz", + "integrity": "sha512-Azwzvl90HaF0aCz1JrDdXQykFakSSNPaPoiZ9fm5qJIMHioDZEi7OAdRwSm6rSoPtY3Qutnm3L7ogmg3dc+wbQ==", "dev": true, - "license": "MIT", + "license": "ISC", "engines": { - "node": ">=0.8.0" + "node": "^18.17.0 || >=20.5.0" } }, - "node_modules/protractor/node_modules/y18n": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", - "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==", - "dev": true, - "license": "ISC" + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "license": "MIT" }, - "node_modules/protractor/node_modules/yargs": { - "version": "15.4.1", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", - "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", - "dev": true, + "node_modules/progress": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", "license": "MIT", - "dependencies": { - "cliui": "^6.0.0", - "decamelize": "^1.2.0", - "find-up": "^4.1.0", - "get-caller-file": "^2.0.1", - "require-directory": "^2.1.1", - "require-main-filename": "^2.0.0", - "set-blocking": "^2.0.0", - "string-width": "^4.2.0", - "which-module": "^2.0.0", - "y18n": "^4.0.0", - "yargs-parser": "^18.1.2" - }, "engines": { - "node": ">=8" + "node": ">=0.4.0" } }, - "node_modules/protractor/node_modules/yargs-parser": { - "version": "18.1.3", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", - "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", + "node_modules/promise-retry": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/promise-retry/-/promise-retry-2.0.1.tgz", + "integrity": "sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==", "dev": true, - "license": "ISC", + "license": "MIT", "dependencies": { - "camelcase": "^5.0.0", - "decamelize": "^1.2.0" + "err-code": "^2.0.2", + "retry": "^0.12.0" }, "engines": { - "node": ">=6" + "node": ">=10" } }, "node_modules/proxy-addr": { @@ -18262,29 +17956,6 @@ "integrity": "sha512-yPw4Sng1gWghHQWj0B3ZggWUm4qVbPwPFcRG8KyxiU7J2OHFSoEHKS+EZ3fv5l1t9CyCiop6l/ZYeWbrgoQejw==", "license": "MIT" }, - "node_modules/psl": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/psl/-/psl-1.15.0.tgz", - "integrity": "sha512-JZd3gMVBAVQkSs6HdNZo9Sdo0LNcQeMNP3CozBJb3JYC/QUYZTnKxP+f8oWRX4rHP5EurWxqAHTSwUCjlNKa1w==", - "dev": true, - "license": "MIT", - "dependencies": { - "punycode": "^2.3.1" - }, - "funding": { - "url": "https://github.com/sponsors/lupomontero" - } - }, - "node_modules/psl/node_modules/punycode": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", - "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, "node_modules/pump": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.3.tgz", @@ -18413,18 +18084,6 @@ "rimraf": "bin.js" } }, - "node_modules/q": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/q/-/q-1.4.1.tgz", - "integrity": "sha512-/CdEdaw49VZVmyIDGUQKDDT53c7qBkO6g5CefWz91Ae+l4+cRtcDYwMTXh6me4O8TMldeGHG3N2Bl84V78Ywbg==", - "deprecated": "You or someone you depend on is using Q, the JavaScript Promise library that gave JavaScript developers strong feelings about promises. They can almost certainly migrate to the native JavaScript promise now. Thank you literally everyone for joining me in this bet against the odds. Be excellent to each other.\n\n(For a CapTP with native promises, see @endo/eventual-send and @endo/captp)", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.6.0", - "teleport": ">=0.2.0" - } - }, "node_modules/qjobs": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/qjobs/-/qjobs-1.2.0.tgz", @@ -18436,9 +18095,9 @@ } }, "node_modules/qs": { - "version": "6.14.1", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.1.tgz", - "integrity": "sha512-4EK3+xJl8Ts67nLYNwqw/dsFVnCf+qR7RgXSK9jEEm9unao3njwMDdmsdvoKBKHzxd7tCYz5e5M+SnMjdtXGQQ==", + "version": "6.14.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.2.tgz", + "integrity": "sha512-V/yCWTTF7VJ9hIh18Ugr2zhJMP01MY7c5kh4J870L7imm6/DIzBsNLTXzMwUA3yZ5b/KBqLx8Kp3uRvd7xSe3Q==", "dev": true, "license": "BSD-3-Clause", "dependencies": { @@ -18482,16 +18141,6 @@ "performance-now": "^2.1.0" } }, - "node_modules/randombytes": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", - "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "safe-buffer": "^5.1.0" - } - }, "node_modules/range-parser": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", @@ -18518,16 +18167,6 @@ "node": ">= 0.10" } }, - "node_modules/raw-body/node_modules/bytes": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", - "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, "node_modules/raw-loader": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/raw-loader/-/raw-loader-4.0.2.tgz", @@ -18550,9 +18189,9 @@ } }, "node_modules/raw-loader/node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz", + "integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==", "dev": true, "license": "MIT", "dependencies": { @@ -18808,67 +18447,6 @@ "regjsparser": "bin/parser" } }, - "node_modules/repeat-element": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.4.tgz", - "integrity": "sha512-LFiNfRcSu7KK3evMyYOuCzv3L10TW7yC1G2/+StMjK8Y6Vqd2MG7r/Qjw4ghtuCOjFvlnms/iMmLqpvW/ES/WQ==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/repeat-string": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", - "integrity": "sha512-PV0dzCYDNfRi1jCDbJzpW7jNNDRuCOG/jI5ctQcGKt/clZD+YcPS3yIlWuTJMmESC8aevCFmWJy5wjAFgNqN6w==", - "license": "MIT", - "engines": { - "node": ">=0.10" - } - }, - "node_modules/request": { - "version": "2.88.2", - "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz", - "integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==", - "deprecated": "request has been deprecated, see https://github.com/request/request/issues/3142", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "aws-sign2": "~0.7.0", - "aws4": "^1.8.0", - "caseless": "~0.12.0", - "combined-stream": "~1.0.6", - "extend": "~3.0.2", - "forever-agent": "~0.6.1", - "form-data": "~2.3.2", - "har-validator": "~5.1.3", - "http-signature": "~1.2.0", - "is-typedarray": "~1.0.0", - "isstream": "~0.1.2", - "json-stringify-safe": "~5.0.1", - "mime-types": "~2.1.19", - "oauth-sign": "~0.9.0", - "performance-now": "^2.1.0", - "qs": "~6.5.2", - "safe-buffer": "^5.1.2", - "tough-cookie": "~2.5.0", - "tunnel-agent": "^0.6.0", - "uuid": "^3.3.2" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/request/node_modules/qs": { - "version": "6.5.3", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.3.tgz", - "integrity": "sha512-qxXIEh4pCGfHICj1mAJQ2/2XVZkjCDTcEgfoSQxc/fYivUZxTkk7L3bDBJSoNrEzXI17oUO5Dp07ktqE5KzczA==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.6" - } - }, "node_modules/require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", @@ -18889,13 +18467,6 @@ "node": ">=0.10.0" } }, - "node_modules/require-main-filename": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", - "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", - "dev": true, - "license": "ISC" - }, "node_modules/requires-port": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", @@ -19101,9 +18672,9 @@ } }, "node_modules/rollup": { - "version": "4.52.3", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.52.3.tgz", - "integrity": "sha512-RIDh866U8agLgiIcdpB+COKnlCreHJLfIhWC3LVflku5YHfpnsIKigRZeFfMfCc4dVcqNVfQQ5gO/afOck064A==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.59.0.tgz", + "integrity": "sha512-2oMpl67a3zCH9H79LeMcbDhXW/UmWG/y2zuqnF2jQq5uq9TbM9TVyXvA4+t+ne2IIkBdrLpAaRQAvo7YI/Yyeg==", "dev": true, "license": "MIT", "dependencies": { @@ -19117,28 +18688,31 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.52.3", - "@rollup/rollup-android-arm64": "4.52.3", - "@rollup/rollup-darwin-arm64": "4.52.3", - "@rollup/rollup-darwin-x64": "4.52.3", - "@rollup/rollup-freebsd-arm64": "4.52.3", - "@rollup/rollup-freebsd-x64": "4.52.3", - "@rollup/rollup-linux-arm-gnueabihf": "4.52.3", - "@rollup/rollup-linux-arm-musleabihf": "4.52.3", - "@rollup/rollup-linux-arm64-gnu": "4.52.3", - "@rollup/rollup-linux-arm64-musl": "4.52.3", - "@rollup/rollup-linux-loong64-gnu": "4.52.3", - "@rollup/rollup-linux-ppc64-gnu": "4.52.3", - "@rollup/rollup-linux-riscv64-gnu": "4.52.3", - "@rollup/rollup-linux-riscv64-musl": "4.52.3", - "@rollup/rollup-linux-s390x-gnu": "4.52.3", - "@rollup/rollup-linux-x64-gnu": "4.52.3", - "@rollup/rollup-linux-x64-musl": "4.52.3", - "@rollup/rollup-openharmony-arm64": "4.52.3", - "@rollup/rollup-win32-arm64-msvc": "4.52.3", - "@rollup/rollup-win32-ia32-msvc": "4.52.3", - "@rollup/rollup-win32-x64-gnu": "4.52.3", - "@rollup/rollup-win32-x64-msvc": "4.52.3", + "@rollup/rollup-android-arm-eabi": "4.59.0", + "@rollup/rollup-android-arm64": "4.59.0", + "@rollup/rollup-darwin-arm64": "4.59.0", + "@rollup/rollup-darwin-x64": "4.59.0", + "@rollup/rollup-freebsd-arm64": "4.59.0", + "@rollup/rollup-freebsd-x64": "4.59.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.59.0", + "@rollup/rollup-linux-arm-musleabihf": "4.59.0", + "@rollup/rollup-linux-arm64-gnu": "4.59.0", + "@rollup/rollup-linux-arm64-musl": "4.59.0", + "@rollup/rollup-linux-loong64-gnu": "4.59.0", + "@rollup/rollup-linux-loong64-musl": "4.59.0", + "@rollup/rollup-linux-ppc64-gnu": "4.59.0", + "@rollup/rollup-linux-ppc64-musl": "4.59.0", + "@rollup/rollup-linux-riscv64-gnu": "4.59.0", + "@rollup/rollup-linux-riscv64-musl": "4.59.0", + "@rollup/rollup-linux-s390x-gnu": "4.59.0", + "@rollup/rollup-linux-x64-gnu": "4.59.0", + "@rollup/rollup-linux-x64-musl": "4.59.0", + "@rollup/rollup-openbsd-x64": "4.59.0", + "@rollup/rollup-openharmony-arm64": "4.59.0", + "@rollup/rollup-win32-arm64-msvc": "4.59.0", + "@rollup/rollup-win32-ia32-msvc": "4.59.0", + "@rollup/rollup-win32-x64-gnu": "4.59.0", + "@rollup/rollup-win32-x64-msvc": "4.59.0", "fsevents": "~2.3.2" } }, @@ -19383,61 +18957,13 @@ } } }, - "node_modules/saucelabs": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/saucelabs/-/saucelabs-1.5.0.tgz", - "integrity": "sha512-jlX3FGdWvYf4Q3LFfFWS1QvPg3IGCGWxIc8QBFdPTbpTJnt/v17FHXYVAn7C8sHf1yUXo2c7yIM0isDryfYtHQ==", - "dev": true, - "dependencies": { - "https-proxy-agent": "^2.2.1" - }, - "engines": { - "node": "*" - } - }, - "node_modules/saucelabs/node_modules/agent-base": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.3.0.tgz", - "integrity": "sha512-salcGninV0nPrwpGNn4VTXBb1SOuXQBiqbrNXoeizJsHrsL6ERFM2Ne3JUSBWRE6aeNJI2ROP/WEEIDUiDe3cg==", - "dev": true, - "license": "MIT", - "dependencies": { - "es6-promisify": "^5.0.0" - }, - "engines": { - "node": ">= 4.0.0" - } - }, - "node_modules/saucelabs/node_modules/debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.1" - } - }, - "node_modules/saucelabs/node_modules/https-proxy-agent": { - "version": "2.2.4", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-2.2.4.tgz", - "integrity": "sha512-OmvfoQ53WLjtA9HeYP9RNrWMJzzAz1JGaSFr1nijg0PVR1JaD/xbJq1mdEIIlxGpXp9eSe/O2LgU9DJmTPd0Eg==", - "dev": true, - "license": "MIT", - "dependencies": { - "agent-base": "^4.3.0", - "debug": "^3.1.0" - }, - "engines": { - "node": ">= 4.5.0" - } - }, "node_modules/sax": { "version": "1.4.4", "resolved": "https://registry.npmjs.org/sax/-/sax-1.4.4.tgz", "integrity": "sha512-1n3r/tGXO6b6VXMdFT54SHzT9ytu9yr7TaELowdYpMqY/Ao7EnlQGmAQ1+RatX7Tkkdm6hONI2owqNx2aZj5Sw==", "dev": true, "license": "BlueOak-1.0.0", + "optional": true, "engines": { "node": ">=11.0.0" } @@ -19487,49 +19013,6 @@ "dev": true, "license": "MIT" }, - "node_modules/selenium-webdriver": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/selenium-webdriver/-/selenium-webdriver-3.6.0.tgz", - "integrity": "sha512-WH7Aldse+2P5bbFBO4Gle/nuQOdVwpHMTL6raL3uuBj/vPG07k6uzt3aiahu352ONBr5xXh0hDlM3LhtXPOC4Q==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "jszip": "^3.1.3", - "rimraf": "^2.5.4", - "tmp": "0.0.30", - "xml2js": "^0.4.17" - }, - "engines": { - "node": ">= 6.9.0" - } - }, - "node_modules/selenium-webdriver/node_modules/rimraf": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", - "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", - "deprecated": "Rimraf versions prior to v4 are no longer supported", - "dev": true, - "license": "ISC", - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - } - }, - "node_modules/selenium-webdriver/node_modules/tmp": { - "version": "0.0.30", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.30.tgz", - "integrity": "sha512-HXdTB7lvMwcb55XFfrTM8CPr/IYREk4hVBFaQ4b/6nInrluSL86hfHm7vu0luYKCfyBZp2trCjpc8caC3vVM3w==", - "dev": true, - "license": "MIT", - "dependencies": { - "os-tmpdir": "~1.0.1" - }, - "engines": { - "node": ">=0.4.0" - } - }, "node_modules/selfsigned": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-2.4.1.tgz", @@ -19601,16 +19084,6 @@ "url": "https://opencollective.com/express" } }, - "node_modules/serialize-javascript": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", - "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "randombytes": "^2.1.0" - } - }, "node_modules/serve-index": { "version": "1.9.2", "resolved": "https://registry.npmjs.org/serve-index/-/serve-index-1.9.2.tgz", @@ -19708,13 +19181,6 @@ "url": "https://opencollective.com/express" } }, - "node_modules/set-blocking": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", - "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", - "dev": true, - "license": "ISC" - }, "node_modules/set-function-length": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", @@ -19810,13 +19276,6 @@ "node": ">=0.10.0" } }, - "node_modules/setimmediate": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", - "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==", - "dev": true, - "license": "MIT" - }, "node_modules/setprototypeof": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", @@ -19959,21 +19418,21 @@ } }, "node_modules/sigstore": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/sigstore/-/sigstore-3.1.0.tgz", - "integrity": "sha512-ZpzWAFHIFqyFE56dXqgX/DkDRZdz+rRcjoIk/RQU4IX0wiCv1l8S7ZrXDHcCc+uaf+6o7w3h2l3g6GYG5TKN9Q==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/sigstore/-/sigstore-4.1.0.tgz", + "integrity": "sha512-/fUgUhYghuLzVT/gaJoeVehLCgZiUxPCPMcyVNY0lIf/cTCz58K/WTI7PefDarXxp9nUKpEwg1yyz3eSBMTtgA==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@sigstore/bundle": "^3.1.0", - "@sigstore/core": "^2.0.0", - "@sigstore/protobuf-specs": "^0.4.0", - "@sigstore/sign": "^3.1.0", - "@sigstore/tuf": "^3.1.0", - "@sigstore/verify": "^2.1.0" + "@sigstore/bundle": "^4.0.0", + "@sigstore/core": "^3.1.0", + "@sigstore/protobuf-specs": "^0.5.0", + "@sigstore/sign": "^4.1.0", + "@sigstore/tuf": "^4.0.1", + "@sigstore/verify": "^3.1.0" }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/slice-ansi": { @@ -20014,73 +19473,23 @@ "license": "MIT", "engines": { "node": ">= 6.0.0", - "npm": ">= 3.0.0" - } - }, - "node_modules/snapdragon": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz", - "integrity": "sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==", - "license": "MIT", - "dependencies": { - "base": "^0.11.1", - "debug": "^2.2.0", - "define-property": "^0.2.5", - "extend-shallow": "^2.0.1", - "map-cache": "^0.2.2", - "source-map": "^0.5.6", - "source-map-resolve": "^0.5.0", - "use": "^3.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon-node": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/snapdragon-node/-/snapdragon-node-2.1.1.tgz", - "integrity": "sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==", - "license": "MIT", - "dependencies": { - "define-property": "^1.0.0", - "isobject": "^3.0.0", - "snapdragon-util": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon-node/node_modules/define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha512-cZTYKFWspt9jZsMscWo8sc/5lbPC9Q0N5nBLgb+Yd915iL3udB1uFgS3B8YCx66UVHq018DAVFoee7x+gxggeA==", - "license": "MIT", - "dependencies": { - "is-descriptor": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon-util": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/snapdragon-util/-/snapdragon-util-3.0.1.tgz", - "integrity": "sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==", - "license": "MIT", - "dependencies": { - "kind-of": "^3.2.0" - }, - "engines": { - "node": ">=0.10.0" + "npm": ">= 3.0.0" } }, - "node_modules/snapdragon-util/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "node_modules/snapdragon": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz", + "integrity": "sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==", "license": "MIT", "dependencies": { - "is-buffer": "^1.1.5" + "base": "^0.11.1", + "debug": "^2.2.0", + "define-property": "^0.2.5", + "extend-shallow": "^2.0.1", + "map-cache": "^0.2.2", + "source-map": "^0.5.6", + "source-map-resolve": "^0.5.0", + "use": "^3.1.0" }, "engines": { "node": ">=0.10.0" @@ -20209,9 +19618,9 @@ } }, "node_modules/socket.io-parser": { - "version": "4.2.5", - "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.5.tgz", - "integrity": "sha512-bPMmpy/5WWKHea5Y/jYAP6k74A+hvmRCQaJuJB6I/ML5JZq/KfNieUVo/3Mh7SAqn7TyFdIo6wqYHInG1MU1bQ==", + "version": "4.2.6", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.6.tgz", + "integrity": "sha512-asJqbVBDsBCJx0pTqw3WfesSY0iRX+2xzWEWzrpcH7L6fLzrhyF8WPI8UaeM4YCuDfpwA/cgsdugMsmtz8EJeg==", "dev": true, "license": "MIT", "dependencies": { @@ -20245,13 +19654,13 @@ } }, "node_modules/socks": { - "version": "2.8.7", - "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.7.tgz", - "integrity": "sha512-HLpt+uLy/pxB+bum/9DzAgiKS8CX1EvbWxI4zlmgGCExImLdiad2iCwXT5Z4c9c3Eq8rP2318mPW2c+QbtjK8A==", + "version": "2.8.8", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.8.tgz", + "integrity": "sha512-NlGELfPrgX2f1TAAcz0WawlLn+0r3FyhhCRpFFK2CemXenPYvzMWWZINv3eDNo9ucdwme7oCHRY0Jnbs4aIkog==", "dev": true, "license": "MIT", "dependencies": { - "ip-address": "^10.0.1", + "ip-address": "^10.1.1", "smart-buffer": "^4.2.0" }, "engines": { @@ -20274,6 +19683,16 @@ "node": ">= 14" } }, + "node_modules/socks/node_modules/ip-address": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.2.0.tgz", + "integrity": "sha512-/+S6j4E9AHvW9SWMSEY9Xfy66O5PWvVEJ08O0y5JGyEKQpojb0K0GKpz/v5HJ/G0vi3D2sjGK78119oXZeE0qA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, "node_modules/source-map": { "version": "0.7.6", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.6.tgz", @@ -20370,28 +19789,6 @@ "deprecated": "See https://github.com/lydell/source-map-url#deprecated", "license": "MIT" }, - "node_modules/spdx-correct": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.2.0.tgz", - "integrity": "sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "spdx-expression-parse": "^3.0.0", - "spdx-license-ids": "^3.0.0" - } - }, - "node_modules/spdx-correct/node_modules/spdx-expression-parse": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", - "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "spdx-exceptions": "^2.1.0", - "spdx-license-ids": "^3.0.0" - } - }, "node_modules/spdx-exceptions": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.5.0.tgz", @@ -20494,43 +19891,17 @@ "node": ">=0.8" } }, - "node_modules/sshpk": { - "version": "1.18.0", - "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.18.0.tgz", - "integrity": "sha512-2p2KJZTSqQ/I3+HX42EpYOa2l3f8Erv8MWKsy2I9uf4wA7yFIkXRffYdsx86y6z4vHtV8u7g+pPlr8/4ouAxsQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "asn1": "~0.2.3", - "assert-plus": "^1.0.0", - "bcrypt-pbkdf": "^1.0.0", - "dashdash": "^1.12.0", - "ecc-jsbn": "~0.1.1", - "getpass": "^0.1.1", - "jsbn": "~0.1.0", - "safer-buffer": "^2.0.2", - "tweetnacl": "~0.14.0" - }, - "bin": { - "sshpk-conv": "bin/sshpk-conv", - "sshpk-sign": "bin/sshpk-sign", - "sshpk-verify": "bin/sshpk-verify" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/ssri": { - "version": "12.0.0", - "resolved": "https://registry.npmjs.org/ssri/-/ssri-12.0.0.tgz", - "integrity": "sha512-S7iGNosepx9RadX82oimUkvr0Ct7IjJbEbs4mJcTxst8um95J3sDYU1RBEOvdu6oL1Wek2ODI5i4MAw+dZ6cAQ==", + "version": "13.0.1", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-13.0.1.tgz", + "integrity": "sha512-QUiRf1+u9wPTL/76GTYlKttDEBWV1ga9ZXW8BG6kfdeyyM8LGPix9gROyg9V2+P0xNyF3X2Go526xKFdMZrHSQ==", "dev": true, "license": "ISC", "dependencies": { "minipass": "^7.0.3" }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/stackblur-canvas": { @@ -20683,39 +20054,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/string-width-cjs": { - "name": "string-width", - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/string-width-cjs/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true, - "license": "MIT" - }, - "node_modules/string-width-cjs/node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/string-width/node_modules/ansi-regex": { "version": "6.2.2", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", @@ -20817,20 +20155,6 @@ "node": ">=8" } }, - "node_modules/strip-ansi-cjs": { - "name": "strip-ansi", - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/strip-bom": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", @@ -20915,94 +20239,32 @@ } }, "node_modules/tar": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz", - "integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==", - "deprecated": "Old versions of tar are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", - "dev": true, - "license": "ISC", - "dependencies": { - "chownr": "^2.0.0", - "fs-minipass": "^2.0.0", - "minipass": "^5.0.0", - "minizlib": "^2.1.1", - "mkdirp": "^1.0.3", - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/tar/node_modules/fs-minipass": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", - "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", - "dev": true, - "license": "ISC", - "dependencies": { - "minipass": "^3.0.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/tar/node_modules/fs-minipass/node_modules/minipass": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "version": "7.5.13", + "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.13.tgz", + "integrity": "sha512-tOG/7GyXpFevhXVh8jOPJrmtRpOTsYqUIkVdVooZYJS/z8WhfQUX8RJILmeuJNinGAMSu1veBr4asSHFt5/hng==", "dev": true, - "license": "ISC", + "license": "BlueOak-1.0.0", "dependencies": { - "yallist": "^4.0.0" + "@isaacs/fs-minipass": "^4.0.0", + "chownr": "^3.0.0", + "minipass": "^7.1.2", + "minizlib": "^3.1.0", + "yallist": "^5.0.0" }, "engines": { - "node": ">=8" + "node": ">=18" } }, - "node_modules/tar/node_modules/minipass": { + "node_modules/tar/node_modules/yallist": { "version": "5.0.0", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", - "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=8" - } - }, - "node_modules/tar/node_modules/minizlib": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", - "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", - "dev": true, - "license": "MIT", - "dependencies": { - "minipass": "^3.0.0", - "yallist": "^4.0.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/tar/node_modules/minizlib/node_modules/minipass": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz", + "integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==", "dev": true, - "license": "ISC", - "dependencies": { - "yallist": "^4.0.0" - }, + "license": "BlueOak-1.0.0", "engines": { - "node": ">=8" + "node": ">=18" } }, - "node_modules/tar/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true, - "license": "ISC" - }, "node_modules/terser": { "version": "5.43.1", "resolved": "https://registry.npmjs.org/terser/-/terser-5.43.1.tgz", @@ -21023,16 +20285,15 @@ } }, "node_modules/terser-webpack-plugin": { - "version": "5.3.16", - "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.16.tgz", - "integrity": "sha512-h9oBFCWrq78NyWWVcSwZarJkZ01c2AyGrzs1crmHZO3QUg9D61Wu4NPjBy69n7JqylFF5y+CsUZYmYEIZ3mR+Q==", + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.5.0.tgz", + "integrity": "sha512-UYhptBwhWvfIjKd/UuFo6D8uq9xpGLDK+z8EDsj/zWhrTaH34cKEbrkMKfV5YWqGBvAYA3tlzZbs2R+qYrbQJA==", "dev": true, "license": "MIT", "dependencies": { "@jridgewell/trace-mapping": "^0.3.25", "jest-worker": "^27.4.5", "schema-utils": "^4.3.0", - "serialize-javascript": "^6.0.2", "terser": "^5.31.1" }, "engines": { @@ -21174,7 +20435,6 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, "license": "MIT", "dependencies": { "is-number": "^7.0.0" @@ -21210,30 +20470,6 @@ "node": ">=0.6" } }, - "node_modules/tough-cookie": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", - "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "psl": "^1.1.28", - "punycode": "^2.1.1" - }, - "engines": { - "node": ">=0.8" - } - }, - "node_modules/tough-cookie/node_modules/punycode": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", - "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, "node_modules/tree-dump": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/tree-dump/-/tree-dump-1.1.0.tgz", @@ -21302,39 +20538,6 @@ "node": ">=4" } }, - "node_modules/ts-loader/node_modules/braces": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", - "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", - "license": "MIT", - "dependencies": { - "arr-flatten": "^1.1.0", - "array-unique": "^0.3.2", - "extend-shallow": "^2.0.1", - "fill-range": "^4.0.0", - "isobject": "^3.0.1", - "repeat-element": "^1.1.2", - "snapdragon": "^0.8.1", - "snapdragon-node": "^2.0.1", - "split-string": "^3.0.2", - "to-regex": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/ts-loader/node_modules/braces/node_modules/extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", - "license": "MIT", - "dependencies": { - "is-extendable": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/ts-loader/node_modules/chalk": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", @@ -21368,78 +20571,18 @@ "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "license": "MIT", - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/ts-loader/node_modules/fill-range": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", - "integrity": "sha512-VcpLTWqWDiTerugjj8e3+esbg+skS3M9e54UuR3iCeIDMXCLTsAH8hTSzDQU/X6/6t3eYkOKoZSef2PlU6U1XQ==", - "license": "MIT", - "dependencies": { - "extend-shallow": "^2.0.1", - "is-number": "^3.0.0", - "repeat-string": "^1.6.1", - "to-regex-range": "^2.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/ts-loader/node_modules/fill-range/node_modules/extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", - "license": "MIT", - "dependencies": { - "is-extendable": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/ts-loader/node_modules/has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/ts-loader/node_modules/is-extendable": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/ts-loader/node_modules/is-number": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", - "integrity": "sha512-4cboCqIpliH+mAvFNegjZQ4kgKc3ZUhQVr3HvWbSh5q3WH2v82ct+T2Y1hdU5Gdtorx/cLifQjqCbL7bpznLTg==", - "license": "MIT", - "dependencies": { - "kind-of": "^3.0.2" - }, + "license": "MIT", "engines": { - "node": ">=0.10.0" + "node": ">=0.8.0" } }, - "node_modules/ts-loader/node_modules/is-number/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "node_modules/ts-loader/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", "license": "MIT", - "dependencies": { - "is-buffer": "^1.1.5" - }, "engines": { - "node": ">=0.10.0" + "node": ">=4" } }, "node_modules/ts-loader/node_modules/json5": { @@ -21513,19 +20656,6 @@ "node": ">=4" } }, - "node_modules/ts-loader/node_modules/to-regex-range": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", - "integrity": "sha512-ZZWNfCjUokXXDGXFpZehJIkZqq91BcULFq/Pi7M5i4JnxXdhMKAK682z8bCW3o8Hj1wuuzoKcW3DfVzaP6VuNg==", - "license": "MIT", - "dependencies": { - "is-number": "^3.0.0", - "repeat-string": "^1.6.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/ts-node": { "version": "10.5.0", "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.5.0.tgz", @@ -21602,40 +20732,20 @@ "license": "0BSD" }, "node_modules/tuf-js": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/tuf-js/-/tuf-js-3.1.0.tgz", - "integrity": "sha512-3T3T04WzowbwV2FDiGXBbr81t64g1MUGGJRgT4x5o97N+8ArdhVCAF9IxFrxuSJmM3E5Asn7nKHkao0ibcZXAg==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/tuf-js/-/tuf-js-4.1.0.tgz", + "integrity": "sha512-50QV99kCKH5P/Vs4E2Gzp7BopNV+KzTXqWeaxrfu5IQJBOULRsTIS9seSsOVT8ZnGXzCyx55nYWAi4qJzpZKEQ==", "dev": true, "license": "MIT", "dependencies": { - "@tufjs/models": "3.0.1", - "debug": "^4.4.1", - "make-fetch-happen": "^14.0.3" - }, - "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/tunnel-agent": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", - "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "safe-buffer": "^5.0.1" + "@tufjs/models": "4.1.0", + "debug": "^4.4.3", + "make-fetch-happen": "^15.0.1" }, "engines": { - "node": "*" + "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/tweetnacl": { - "version": "0.14.5", - "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", - "integrity": "sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==", - "dev": true, - "license": "Unlicense" - }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", @@ -21851,6 +20961,16 @@ "integrity": "sha512-+A5Sja4HP1M08MaXya7p5LvjuM7K6q/2EaC0+iovj/wOcMsTzMvDFbasi/oSapiwOlt252IqsKqPjCl7huKS0A==", "license": "MIT" }, + "node_modules/undici": { + "version": "6.25.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-6.25.0.tgz", + "integrity": "sha512-ZgpWDC5gmNiuY9CnLVXEH8rl50xhRCuLNA97fAUnKi8RRuV4E6KG31pDTsLVUKnohJE0I3XDrTeEydAXRw47xg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.17" + } + }, "node_modules/undici-types": { "version": "6.21.0", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", @@ -21914,398 +21034,801 @@ "set-value": "^2.0.1" }, "engines": { - "node": ">=0.10.0" + "node": ">=0.10.0" + } + }, + "node_modules/union-value/node_modules/is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/unset-value": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz", + "integrity": "sha512-PcA2tsuGSF9cnySLHTLSh2qrQiJ70mn+r+Glzxv2TWZblxsxCC52BDlZoPCsz7STd9pN7EZetkWZBAvk4cgZdQ==", + "license": "MIT", + "dependencies": { + "has-value": "^0.3.1", + "isobject": "^3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/unset-value/node_modules/has-value": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/has-value/-/has-value-0.3.1.tgz", + "integrity": "sha512-gpG936j8/MzaeID5Yif+577c17TxaDmhuyVgSwtnL/q8UUTySg8Mecb+8Cf1otgLoD7DDH75axp86ER7LFsf3Q==", + "license": "MIT", + "dependencies": { + "get-value": "^2.0.3", + "has-values": "^0.1.4", + "isobject": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/unset-value/node_modules/has-value/node_modules/isobject": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", + "integrity": "sha512-+OUdGJlgjOBZDfxnDjYYG6zp487z0JGNQq3cYQYg5f5hKR+syHMsaztzGeml/4kGG55CSpKSpWTY+jYGgsHLgA==", + "license": "MIT", + "dependencies": { + "isarray": "1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/unset-value/node_modules/has-values": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/has-values/-/has-values-0.1.4.tgz", + "integrity": "sha512-J8S0cEdWuQbqD9//tlZxiMuMNmxB8PlEwvYwuxsTmR1G5RXUePEX/SJn7aD0GMLieuZYSwNH0cQuJGwnYunXRQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/unset-value/node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "license": "MIT" + }, + "node_modules/update-browserslist-db": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/uri-js/node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/urix": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz", + "integrity": "sha512-Am1ousAhSLBeB9cG/7k7r2R0zj50uDRlZHPGbazid5s9rlF1F/QKYObEKSIunSjIOkJZqwRRLpvewjEkM7pSqg==", + "deprecated": "Please see https://github.com/lydell/urix#deprecated", + "license": "MIT" + }, + "node_modules/use": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz", + "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/util": { + "version": "0.12.4", + "resolved": "https://registry.npmjs.org/util/-/util-0.12.4.tgz", + "integrity": "sha512-bxZ9qtSlGUWSOy9Qa9Xgk11kSslpuZwaxCg4sNIDj6FLucDab2JxnHwyNTCpHMtK1MjoQiWQ6DiUMZYbSrO+Sw==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "is-arguments": "^1.0.4", + "is-generator-function": "^1.0.7", + "is-typed-array": "^1.1.3", + "safe-buffer": "^5.1.2", + "which-typed-array": "^1.1.2" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "license": "MIT" + }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/utrie": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/utrie/-/utrie-1.0.2.tgz", + "integrity": "sha512-1MLa5ouZiOmQzUbjbu9VmjLzn1QLXBhwpUa7kdLUQK+KQ5KA9I1vk5U4YHe/X2Ch7PYnJfWuWT+VbuxbGwljhw==", + "license": "MIT", + "dependencies": { + "base64-arraybuffer": "^1.0.2" + } + }, + "node_modules/v8-compile-cache-lib": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", + "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", + "dev": true, + "license": "MIT" + }, + "node_modules/validate-npm-package-name": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-6.0.2.tgz", + "integrity": "sha512-IUoow1YUtvoBBC06dXs8bR8B9vuA3aJfmQNKMoaPG/OFsPmoQvw8xh+6Ye25Gx9DQhoEom3Pcu9MKHerm/NpUQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/validator": { + "version": "13.15.26", + "resolved": "https://registry.npmjs.org/validator/-/validator-13.15.26.tgz", + "integrity": "sha512-spH26xU080ydGggxRyR1Yhcbgx+j3y5jbNXk/8L+iRvdIEQ4uTRH2Sgf2dokud6Q4oAtsbNvJ1Ft+9xmm6IZcA==", + "license": "MIT", + "engines": { + "node": ">= 0.10" } }, - "node_modules/union-value/node_modules/is-extendable": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==", + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", "license": "MIT", "engines": { - "node": ">=0.10.0" + "node": ">= 0.8" } }, - "node_modules/unique-filename": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-4.0.0.tgz", - "integrity": "sha512-XSnEewXmQ+veP7xX2dS5Q4yZAvO40cBN2MWkJ7D/6sW4Dg6wYBNwM1Vrnz1FhH5AdeLIlUXRI9e28z1YZi71NQ==", + "node_modules/vite": { + "version": "7.3.2", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.2.tgz", + "integrity": "sha512-Bby3NOsna2jsjfLVOHKes8sGwgl4TT0E6vvpYgnAYDIF/tie7MRaFthmKuHx1NSXjiTueXH3do80FMQgvEktRg==", "dev": true, - "license": "ISC", + "license": "MIT", "dependencies": { - "unique-slug": "^5.0.0" + "esbuild": "^0.27.0", + "fdir": "^6.5.0", + "picomatch": "^4.0.3", + "postcss": "^8.5.6", + "rollup": "^4.43.0", + "tinyglobby": "^0.2.15" + }, + "bin": { + "vite": "bin/vite.js" }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "lightningcss": "^1.21.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } } }, - "node_modules/unique-slug": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-5.0.0.tgz", - "integrity": "sha512-9OdaqO5kwqR+1kVgHAhsp5vPNU0hnxRa26rBFNfNgM7M6pNtgzeBn3s/xbyCQL3dcjzOatcef6UUHpB/6MaETg==", + "node_modules/vite/node_modules/@esbuild/aix-ppc64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.7.tgz", + "integrity": "sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg==", + "cpu": [ + "ppc64" + ], "dev": true, - "license": "ISC", - "dependencies": { - "imurmurhash": "^0.1.4" - }, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": ">=18" } }, - "node_modules/universalify": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", - "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", + "node_modules/vite/node_modules/@esbuild/android-arm": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.7.tgz", + "integrity": "sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ==", + "cpu": [ + "arm" + ], "dev": true, "license": "MIT", + "optional": true, + "os": [ + "android" + ], "engines": { - "node": ">= 4.0.0" + "node": ">=18" } }, - "node_modules/unpipe": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", - "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "node_modules/vite/node_modules/@esbuild/android-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.7.tgz", + "integrity": "sha512-62dPZHpIXzvChfvfLJow3q5dDtiNMkwiRzPylSCfriLvZeq0a1bWChrGx/BbUbPwOrsWKMn8idSllklzBy+dgQ==", + "cpu": [ + "arm64" + ], "dev": true, "license": "MIT", + "optional": true, + "os": [ + "android" + ], "engines": { - "node": ">= 0.8" + "node": ">=18" } }, - "node_modules/unset-value": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz", - "integrity": "sha512-PcA2tsuGSF9cnySLHTLSh2qrQiJ70mn+r+Glzxv2TWZblxsxCC52BDlZoPCsz7STd9pN7EZetkWZBAvk4cgZdQ==", + "node_modules/vite/node_modules/@esbuild/android-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.7.tgz", + "integrity": "sha512-x5VpMODneVDb70PYV2VQOmIUUiBtY3D3mPBG8NxVk5CogneYhkR7MmM3yR/uMdITLrC1ml/NV1rj4bMJuy9MCg==", + "cpu": [ + "x64" + ], + "dev": true, "license": "MIT", - "dependencies": { - "has-value": "^0.3.1", - "isobject": "^3.0.0" - }, + "optional": true, + "os": [ + "android" + ], "engines": { - "node": ">=0.10.0" + "node": ">=18" } }, - "node_modules/unset-value/node_modules/has-value": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/has-value/-/has-value-0.3.1.tgz", - "integrity": "sha512-gpG936j8/MzaeID5Yif+577c17TxaDmhuyVgSwtnL/q8UUTySg8Mecb+8Cf1otgLoD7DDH75axp86ER7LFsf3Q==", + "node_modules/vite/node_modules/@esbuild/darwin-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.7.tgz", + "integrity": "sha512-5lckdqeuBPlKUwvoCXIgI2D9/ABmPq3Rdp7IfL70393YgaASt7tbju3Ac+ePVi3KDH6N2RqePfHnXkaDtY9fkw==", + "cpu": [ + "arm64" + ], + "dev": true, "license": "MIT", - "dependencies": { - "get-value": "^2.0.3", - "has-values": "^0.1.4", - "isobject": "^2.0.0" - }, + "optional": true, + "os": [ + "darwin" + ], "engines": { - "node": ">=0.10.0" + "node": ">=18" } }, - "node_modules/unset-value/node_modules/has-value/node_modules/isobject": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", - "integrity": "sha512-+OUdGJlgjOBZDfxnDjYYG6zp487z0JGNQq3cYQYg5f5hKR+syHMsaztzGeml/4kGG55CSpKSpWTY+jYGgsHLgA==", + "node_modules/vite/node_modules/@esbuild/darwin-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.7.tgz", + "integrity": "sha512-rYnXrKcXuT7Z+WL5K980jVFdvVKhCHhUwid+dDYQpH+qu+TefcomiMAJpIiC2EM3Rjtq0sO3StMV/+3w3MyyqQ==", + "cpu": [ + "x64" + ], + "dev": true, "license": "MIT", - "dependencies": { - "isarray": "1.0.0" - }, + "optional": true, + "os": [ + "darwin" + ], "engines": { - "node": ">=0.10.0" + "node": ">=18" } }, - "node_modules/unset-value/node_modules/has-values": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/has-values/-/has-values-0.1.4.tgz", - "integrity": "sha512-J8S0cEdWuQbqD9//tlZxiMuMNmxB8PlEwvYwuxsTmR1G5RXUePEX/SJn7aD0GMLieuZYSwNH0cQuJGwnYunXRQ==", + "node_modules/vite/node_modules/@esbuild/freebsd-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.7.tgz", + "integrity": "sha512-B48PqeCsEgOtzME2GbNM2roU29AMTuOIN91dsMO30t+Ydis3z/3Ngoj5hhnsOSSwNzS+6JppqWsuhTp6E82l2w==", + "cpu": [ + "arm64" + ], + "dev": true, "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], "engines": { - "node": ">=0.10.0" + "node": ">=18" } }, - "node_modules/unset-value/node_modules/isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", - "license": "MIT" - }, - "node_modules/update-browserslist-db": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", - "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", + "node_modules/vite/node_modules/@esbuild/freebsd-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.7.tgz", + "integrity": "sha512-jOBDK5XEjA4m5IJK3bpAQF9/Lelu/Z9ZcdhTRLf4cajlB+8VEhFFRjWgfy3M1O4rO2GQ/b2dLwCUGpiF/eATNQ==", + "cpu": [ + "x64" + ], "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-arm": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.7.tgz", + "integrity": "sha512-RkT/YXYBTSULo3+af8Ib0ykH8u2MBh57o7q/DAs3lTJlyVQkgQvlrPTnjIzzRPQyavxtPtfg0EopvDyIt0j1rA==", + "cpu": [ + "arm" ], + "dev": true, "license": "MIT", - "dependencies": { - "escalade": "^3.2.0", - "picocolors": "^1.1.1" - }, - "bin": { - "update-browserslist-db": "cli.js" - }, - "peerDependencies": { - "browserslist": ">= 4.21.0" + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" } }, - "node_modules/uri-js": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", - "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "node_modules/vite/node_modules/@esbuild/linux-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.7.tgz", + "integrity": "sha512-RZPHBoxXuNnPQO9rvjh5jdkRmVizktkT7TCDkDmQ0W2SwHInKCAV95GRuvdSvA7w4VMwfCjUiPwDi0ZO6Nfe9A==", + "cpu": [ + "arm64" + ], "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "punycode": "^2.1.0" + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" } }, - "node_modules/uri-js/node_modules/punycode": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", - "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "node_modules/vite/node_modules/@esbuild/linux-ia32": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.7.tgz", + "integrity": "sha512-GA48aKNkyQDbd3KtkplYWT102C5sn/EZTY4XROkxONgruHPU72l+gW+FfF8tf2cFjeHaRbWpOYa/uRBz/Xq1Pg==", + "cpu": [ + "ia32" + ], "dev": true, "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=6" + "node": ">=18" } }, - "node_modules/urix": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz", - "integrity": "sha512-Am1ousAhSLBeB9cG/7k7r2R0zj50uDRlZHPGbazid5s9rlF1F/QKYObEKSIunSjIOkJZqwRRLpvewjEkM7pSqg==", - "deprecated": "Please see https://github.com/lydell/urix#deprecated", - "license": "MIT" - }, - "node_modules/use": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz", - "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==", + "node_modules/vite/node_modules/@esbuild/linux-loong64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.7.tgz", + "integrity": "sha512-a4POruNM2oWsD4WKvBSEKGIiWQF8fZOAsycHOt6JBpZ+JN2n2JH9WAv56SOyu9X5IqAjqSIPTaJkqN8F7XOQ5Q==", + "cpu": [ + "loong64" + ], + "dev": true, "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=0.10.0" + "node": ">=18" } }, - "node_modules/util": { - "version": "0.12.4", - "resolved": "https://registry.npmjs.org/util/-/util-0.12.4.tgz", - "integrity": "sha512-bxZ9qtSlGUWSOy9Qa9Xgk11kSslpuZwaxCg4sNIDj6FLucDab2JxnHwyNTCpHMtK1MjoQiWQ6DiUMZYbSrO+Sw==", + "node_modules/vite/node_modules/@esbuild/linux-mips64el": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.7.tgz", + "integrity": "sha512-KabT5I6StirGfIz0FMgl1I+R1H73Gp0ofL9A3nG3i/cYFJzKHhouBV5VWK1CSgKvVaG4q1RNpCTR2LuTVB3fIw==", + "cpu": [ + "mips64el" + ], + "dev": true, "license": "MIT", - "dependencies": { - "inherits": "^2.0.3", - "is-arguments": "^1.0.4", - "is-generator-function": "^1.0.7", - "is-typed-array": "^1.1.3", - "safe-buffer": "^5.1.2", - "which-typed-array": "^1.1.2" + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" } }, - "node_modules/util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", - "license": "MIT" - }, - "node_modules/utils-merge": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", - "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "node_modules/vite/node_modules/@esbuild/linux-ppc64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.7.tgz", + "integrity": "sha512-gRsL4x6wsGHGRqhtI+ifpN/vpOFTQtnbsupUF5R5YTAg+y/lKelYR1hXbnBdzDjGbMYjVJLJTd2OFmMewAgwlQ==", + "cpu": [ + "ppc64" + ], "dev": true, "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">= 0.4.0" + "node": ">=18" } }, - "node_modules/utrie": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/utrie/-/utrie-1.0.2.tgz", - "integrity": "sha512-1MLa5ouZiOmQzUbjbu9VmjLzn1QLXBhwpUa7kdLUQK+KQ5KA9I1vk5U4YHe/X2Ch7PYnJfWuWT+VbuxbGwljhw==", + "node_modules/vite/node_modules/@esbuild/linux-riscv64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.7.tgz", + "integrity": "sha512-hL25LbxO1QOngGzu2U5xeXtxXcW+/GvMN3ejANqXkxZ/opySAZMrc+9LY/WyjAan41unrR3YrmtTsUpwT66InQ==", + "cpu": [ + "riscv64" + ], + "dev": true, "license": "MIT", - "dependencies": { - "base64-arraybuffer": "^1.0.2" + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" } }, - "node_modules/uuid": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", - "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", - "deprecated": "Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details.", + "node_modules/vite/node_modules/@esbuild/linux-s390x": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.7.tgz", + "integrity": "sha512-2k8go8Ycu1Kb46vEelhu1vqEP+UeRVj2zY1pSuPdgvbd5ykAw82Lrro28vXUrRmzEsUV0NzCf54yARIK8r0fdw==", + "cpu": [ + "s390x" + ], "dev": true, "license": "MIT", - "bin": { - "uuid": "bin/uuid" + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" } }, - "node_modules/v8-compile-cache-lib": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", - "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", + "node_modules/vite/node_modules/@esbuild/linux-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.7.tgz", + "integrity": "sha512-hzznmADPt+OmsYzw1EE33ccA+HPdIqiCRq7cQeL1Jlq2gb1+OyWBkMCrYGBJ+sxVzve2ZJEVeePbLM2iEIZSxA==", + "cpu": [ + "x64" + ], "dev": true, - "license": "MIT" + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } }, - "node_modules/validate-npm-package-license": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", - "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", + "node_modules/vite/node_modules/@esbuild/netbsd-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.7.tgz", + "integrity": "sha512-b6pqtrQdigZBwZxAn1UpazEisvwaIDvdbMbmrly7cDTMFnw/+3lVxxCTGOrkPVnsYIosJJXAsILG9XcQS+Yu6w==", + "cpu": [ + "arm64" + ], "dev": true, - "license": "Apache-2.0", - "dependencies": { - "spdx-correct": "^3.0.0", - "spdx-expression-parse": "^3.0.0" + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" } }, - "node_modules/validate-npm-package-license/node_modules/spdx-expression-parse": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", - "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", + "node_modules/vite/node_modules/@esbuild/netbsd-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.7.tgz", + "integrity": "sha512-OfatkLojr6U+WN5EDYuoQhtM+1xco+/6FSzJJnuWiUw5eVcicbyK3dq5EeV/QHT1uy6GoDhGbFpprUiHUYggrw==", + "cpu": [ + "x64" + ], "dev": true, "license": "MIT", - "dependencies": { - "spdx-exceptions": "^2.1.0", - "spdx-license-ids": "^3.0.0" + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" } }, - "node_modules/validate-npm-package-name": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-6.0.2.tgz", - "integrity": "sha512-IUoow1YUtvoBBC06dXs8bR8B9vuA3aJfmQNKMoaPG/OFsPmoQvw8xh+6Ye25Gx9DQhoEom3Pcu9MKHerm/NpUQ==", + "node_modules/vite/node_modules/@esbuild/openbsd-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.7.tgz", + "integrity": "sha512-AFuojMQTxAz75Fo8idVcqoQWEHIXFRbOc1TrVcFSgCZtQfSdc1RXgB3tjOn/krRHENUB4j00bfGjyl2mJrU37A==", + "cpu": [ + "arm64" + ], "dev": true, - "license": "ISC", + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": ">=18" } }, - "node_modules/validator": { - "version": "13.15.26", - "resolved": "https://registry.npmjs.org/validator/-/validator-13.15.26.tgz", - "integrity": "sha512-spH26xU080ydGggxRyR1Yhcbgx+j3y5jbNXk/8L+iRvdIEQ4uTRH2Sgf2dokud6Q4oAtsbNvJ1Ft+9xmm6IZcA==", + "node_modules/vite/node_modules/@esbuild/openbsd-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.7.tgz", + "integrity": "sha512-+A1NJmfM8WNDv5CLVQYJ5PshuRm/4cI6WMZRg1by1GwPIQPCTs1GLEUHwiiQGT5zDdyLiRM/l1G0Pv54gvtKIg==", + "cpu": [ + "x64" + ], + "dev": true, "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], "engines": { - "node": ">= 0.10" + "node": ">=18" } }, - "node_modules/vary": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", - "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "node_modules/vite/node_modules/@esbuild/openharmony-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.7.tgz", + "integrity": "sha512-+KrvYb/C8zA9CU/g0sR6w2RBw7IGc5J2BPnc3dYc5VJxHCSF1yNMxTV5LQ7GuKteQXZtspjFbiuW5/dOj7H4Yw==", + "cpu": [ + "arm64" + ], + "dev": true, "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], "engines": { - "node": ">= 0.8" + "node": ">=18" } }, - "node_modules/verror": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", - "integrity": "sha512-ZZKSmDAEFOijERBLkmYfJ+vmk3w+7hOLYDNkRCuRuMJGEmqYNCNLyBBFwWKVMhfwaEF3WOd0Zlw86U/WC/+nYw==", + "node_modules/vite/node_modules/@esbuild/sunos-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.7.tgz", + "integrity": "sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA==", + "cpu": [ + "x64" + ], "dev": true, - "engines": [ - "node >=0.6.0" + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/win32-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.7.tgz", + "integrity": "sha512-7yRhbHvPqSpRUV7Q20VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK3pWFrHeNfDd1VKgyP4O+ng17CA==", + "cpu": [ + "arm64" ], + "dev": true, "license": "MIT", - "dependencies": { - "assert-plus": "^1.0.0", - "core-util-is": "1.0.2", - "extsprintf": "^1.2.0" + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" } }, - "node_modules/verror/node_modules/core-util-is": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==", + "node_modules/vite/node_modules/@esbuild/win32-ia32": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.7.tgz", + "integrity": "sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw==", + "cpu": [ + "ia32" + ], "dev": true, - "license": "MIT" + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } }, - "node_modules/vite": { - "version": "7.1.11", - "resolved": "https://registry.npmjs.org/vite/-/vite-7.1.11.tgz", - "integrity": "sha512-uzcxnSDVjAopEUjljkWh8EIrg6tlzrjFUfMcR1EVsRDGwf/ccef0qQPRyOrROwhrTDaApueq+ja+KLPlzR/zdg==", + "node_modules/vite/node_modules/@esbuild/win32-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.7.tgz", + "integrity": "sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg==", + "cpu": [ + "x64" + ], "dev": true, "license": "MIT", - "dependencies": { - "esbuild": "^0.25.0", - "fdir": "^6.5.0", - "picomatch": "^4.0.3", - "postcss": "^8.5.6", - "rollup": "^4.43.0", - "tinyglobby": "^0.2.15" - }, - "bin": { - "vite": "bin/vite.js" - }, + "optional": true, + "os": [ + "win32" + ], "engines": { - "node": "^20.19.0 || >=22.12.0" - }, - "funding": { - "url": "https://github.com/vitejs/vite?sponsor=1" - }, - "optionalDependencies": { - "fsevents": "~2.3.3" + "node": ">=18" + } + }, + "node_modules/vite/node_modules/esbuild": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.7.tgz", + "integrity": "sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" }, - "peerDependencies": { - "@types/node": "^20.19.0 || >=22.12.0", - "jiti": ">=1.21.0", - "less": "^4.0.0", - "lightningcss": "^1.21.0", - "sass": "^1.70.0", - "sass-embedded": "^1.70.0", - "stylus": ">=0.54.8", - "sugarss": "^5.0.0", - "terser": "^5.16.0", - "tsx": "^4.8.1", - "yaml": "^2.4.2" + "engines": { + "node": ">=18" }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - }, - "jiti": { - "optional": true - }, - "less": { - "optional": true - }, - "lightningcss": { - "optional": true - }, - "sass": { - "optional": true - }, - "sass-embedded": { - "optional": true - }, - "stylus": { - "optional": true - }, - "sugarss": { - "optional": true - }, - "terser": { - "optional": true - }, - "tsx": { - "optional": true - }, - "yaml": { - "optional": true - } + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.27.7", + "@esbuild/android-arm": "0.27.7", + "@esbuild/android-arm64": "0.27.7", + "@esbuild/android-x64": "0.27.7", + "@esbuild/darwin-arm64": "0.27.7", + "@esbuild/darwin-x64": "0.27.7", + "@esbuild/freebsd-arm64": "0.27.7", + "@esbuild/freebsd-x64": "0.27.7", + "@esbuild/linux-arm": "0.27.7", + "@esbuild/linux-arm64": "0.27.7", + "@esbuild/linux-ia32": "0.27.7", + "@esbuild/linux-loong64": "0.27.7", + "@esbuild/linux-mips64el": "0.27.7", + "@esbuild/linux-ppc64": "0.27.7", + "@esbuild/linux-riscv64": "0.27.7", + "@esbuild/linux-s390x": "0.27.7", + "@esbuild/linux-x64": "0.27.7", + "@esbuild/netbsd-arm64": "0.27.7", + "@esbuild/netbsd-x64": "0.27.7", + "@esbuild/openbsd-arm64": "0.27.7", + "@esbuild/openbsd-x64": "0.27.7", + "@esbuild/openharmony-arm64": "0.27.7", + "@esbuild/sunos-x64": "0.27.7", + "@esbuild/win32-arm64": "0.27.7", + "@esbuild/win32-ia32": "0.27.7", + "@esbuild/win32-x64": "0.27.7" } }, "node_modules/vite/node_modules/tinyglobby": { - "version": "0.2.15", - "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", - "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "version": "0.2.16", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.16.tgz", + "integrity": "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==", "dev": true, "license": "MIT", "dependencies": { "fdir": "^6.5.0", - "picomatch": "^4.0.3" + "picomatch": "^4.0.4" }, "engines": { "node": ">=12.0.0" @@ -22362,147 +21885,6 @@ "integrity": "sha512-TOMFWtQdxzjWp8qx4DAraTWTsdhxVSiWa6NkPFSaPtZ1diKUxTn4yTix73A1euG1WbSOMMPcY51cnjTIHrGtDA==", "license": "Apache-2.0" }, - "node_modules/webdriver-js-extender": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/webdriver-js-extender/-/webdriver-js-extender-2.1.0.tgz", - "integrity": "sha512-lcUKrjbBfCK6MNsh7xaY2UAUmZwe+/ib03AjVOpFobX4O7+83BUveSrLfU0Qsyb1DaKJdQRbuU+kM9aZ6QUhiQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/selenium-webdriver": "^3.0.0", - "selenium-webdriver": "^3.0.1" - }, - "engines": { - "node": ">=6.9.x" - } - }, - "node_modules/webdriver-manager": { - "version": "12.1.9", - "resolved": "https://registry.npmjs.org/webdriver-manager/-/webdriver-manager-12.1.9.tgz", - "integrity": "sha512-Yl113uKm8z4m/KMUVWHq1Sjtla2uxEBtx2Ue3AmIlnlPAKloDn/Lvmy6pqWCUersVISpdMeVpAaGbNnvMuT2LQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "adm-zip": "^0.5.2", - "chalk": "^1.1.1", - "del": "^2.2.0", - "glob": "^7.0.3", - "ini": "^1.3.4", - "minimist": "^1.2.0", - "q": "^1.4.1", - "request": "^2.87.0", - "rimraf": "^2.5.2", - "semver": "^5.3.0", - "xml2js": "^0.4.17" - }, - "bin": { - "webdriver-manager": "bin/webdriver-manager" - }, - "engines": { - "node": ">=6.9.x" - } - }, - "node_modules/webdriver-manager/node_modules/ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/webdriver-manager/node_modules/ansi-styles": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", - "integrity": "sha512-kmCevFghRiWM7HB5zTPULl4r9bVFSWjz62MhqizDGUrq2NWuNMQyuv4tHHoKJHs69M/MF64lEcHdYIocrdWQYA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/webdriver-manager/node_modules/chalk": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha512-U3lRVLMSlsCfjqYPbLyVv11M9CPW4I728d6TCKMAOJueEeB9/8o+eSsMnxPJD+Q+K909sdESg7C+tIkoH6on1A==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^2.2.1", - "escape-string-regexp": "^1.0.2", - "has-ansi": "^2.0.0", - "strip-ansi": "^3.0.0", - "supports-color": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/webdriver-manager/node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/webdriver-manager/node_modules/ini": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", - "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", - "dev": true, - "license": "ISC" - }, - "node_modules/webdriver-manager/node_modules/rimraf": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", - "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", - "deprecated": "Rimraf versions prior to v4 are no longer supported", - "dev": true, - "license": "ISC", - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - } - }, - "node_modules/webdriver-manager/node_modules/semver": { - "version": "5.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", - "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver" - } - }, - "node_modules/webdriver-manager/node_modules/strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/webdriver-manager/node_modules/supports-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha512-KKNVtd6pCYgPIKU4cp2733HWYCpplQhddZLBUryaAHou723x+FRzQ5Df824Fj+IyyuiQTRoub4SnIFfIcrp70g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.8.0" - } - }, "node_modules/webpack": { "version": "5.105.0", "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.105.0.tgz", @@ -22718,16 +22100,6 @@ "npm": "1.2.8000 || >= 1.4.16" } }, - "node_modules/webpack-dev-server/node_modules/bytes": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", - "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, "node_modules/webpack-dev-server/node_modules/chokidar": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", @@ -22753,25 +22125,6 @@ "fsevents": "~2.3.2" } }, - "node_modules/webpack-dev-server/node_modules/compression": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/compression/-/compression-1.8.1.tgz", - "integrity": "sha512-9mAqGPHLakhCLeNyxPkK4xVo746zQ/czLH1Ky+vkitMnWfWZps8r0qXuwhwizagCRttsL4lfG4pIOvaWLpAP0w==", - "dev": true, - "license": "MIT", - "dependencies": { - "bytes": "3.1.2", - "compressible": "~2.0.18", - "debug": "2.6.9", - "negotiator": "~0.6.4", - "on-headers": "~1.1.0", - "safe-buffer": "5.2.1", - "vary": "~1.1.2" - }, - "engines": { - "node": ">= 0.8.0" - } - }, "node_modules/webpack-dev-server/node_modules/content-disposition": { "version": "0.5.4", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", @@ -22979,37 +22332,17 @@ "dev": true, "license": "MIT" }, - "node_modules/webpack-dev-server/node_modules/negotiator": { - "version": "0.6.4", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.4.tgz", - "integrity": "sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/webpack-dev-server/node_modules/on-headers": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.1.0.tgz", - "integrity": "sha512-737ZY3yNnXy37FHkQxPzt4UZ2UWPWiCZWLvFZ4fu5cueciegX0zGPnrlY6bwRg4FdQOe9YU8MkmJwGhoMybl8A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, "node_modules/webpack-dev-server/node_modules/path-to-regexp": { - "version": "0.1.12", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", - "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==", + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.13.tgz", + "integrity": "sha512-A/AGNMFN3c8bOlvV9RreMdrv7jsmF9XIfDeCd87+I8RNg6s78BhJxMu69NEMHBSJFxKidViTEdruRwEk/WIKqA==", "dev": true, "license": "MIT" }, "node_modules/webpack-dev-server/node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", "dev": true, "license": "MIT", "engines": { @@ -23372,13 +22705,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/which-module": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.1.tgz", - "integrity": "sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==", - "dev": true, - "license": "ISC" - }, "node_modules/which-typed-array": { "version": "1.1.20", "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.20.tgz", @@ -23450,57 +22776,6 @@ "node": ">=8" } }, - "node_modules/wrap-ansi-cjs": { - "name": "wrap-ansi", - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true, - "license": "MIT" - }, - "node_modules/wrap-ansi-cjs/node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/wrap-ansi-cjs/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/wrap-ansi/node_modules/emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", @@ -23603,30 +22878,6 @@ "node": ">=0.8" } }, - "node_modules/xml2js": { - "version": "0.4.23", - "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.23.tgz", - "integrity": "sha512-ySPiMjM0+pLDftHgXY4By0uswI3SPKLDw/i3UXbnO8M/p28zqexCUoPmQFrYD+/1BzhGJSs2i1ERWKJAtiLrug==", - "dev": true, - "license": "MIT", - "dependencies": { - "sax": ">=0.6.0", - "xmlbuilder": "~11.0.0" - }, - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/xmlbuilder": { - "version": "11.0.1", - "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz", - "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4.0" - } - }, "node_modules/y18n": { "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", @@ -23749,9 +23000,9 @@ } }, "node_modules/zod": { - "version": "3.25.76", - "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", - "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", + "version": "4.1.13", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.1.13.tgz", + "integrity": "sha512-AvvthqfqrAhNH9dnfmrfKzX5upOdjUVJYFqNSlkmGf64gRaTzlPwz99IHYnVs28qYAybvAlBV+H7pn0saFY4Ig==", "dev": true, "license": "MIT", "funding": { @@ -23759,13 +23010,13 @@ } }, "node_modules/zod-to-json-schema": { - "version": "3.25.1", - "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.25.1.tgz", - "integrity": "sha512-pM/SU9d3YAggzi6MtR4h7ruuQlqKtad8e9S0fmxcMi+ueAK5Korys/aWcV9LIIHTVbj01NdzxcnXSN+O74ZIVA==", + "version": "3.25.2", + "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.25.2.tgz", + "integrity": "sha512-O/PgfnpT1xKSDeQYSCfRI5Gy3hPf91mKVDuYLUHZJMiDFptvP41MSnWofm8dnCm0256ZNfZIM7DSzuSMAFnjHA==", "dev": true, "license": "ISC", "peerDependencies": { - "zod": "^3.25 || ^4" + "zod": "^3.25.28 || ^4" } }, "node_modules/zone.js": { diff --git a/package.json b/package.json index 739a3b9af..7ac73b101 100644 --- a/package.json +++ b/package.json @@ -33,20 +33,20 @@ }, "private": true, "dependencies": { - "@angular/animations": "20.3.14", + "@angular/animations": "^20.3.19", "@angular/cdk": "^20.2.14", - "@angular/common": "20.3.14", - "@angular/compiler": "20.3.14", - "@angular/core": "20.3.14", - "@angular/forms": "20.3.14", + "@angular/common": "^20.3.19", + "@angular/compiler": "^20.3.19", + "@angular/core": "^20.3.19", + "@angular/forms": "^20.3.19", "@angular/material": "^20.2.14", - "@angular/platform-browser": "20.3.14", - "@angular/platform-browser-dynamic": "20.3.14", - "@angular/platform-server": "20.3.14", - "@angular/router": "20.3.14", - "@angular/ssr": "^20.3.12", + "@angular/platform-browser": "^20.3.19", + "@angular/platform-browser-dynamic": "^20.3.19", + "@angular/platform-server": "^20.3.19", + "@angular/router": "^20.3.19", + "@angular/ssr": "^20.3.25", "@types/hammerjs": "2.0.36", - "compression": "1.7.3", + "compression": "^1.8.1", "cronstrue": "1.94.0", "deep-equal": "^2.2.3", "defiant.js": "2.2.6", @@ -59,13 +59,14 @@ "jsonpath": "^1.1.1", "jspdf": "^2.5.1", "jspdf-autotable": "^3.8.2", - "lodash": "4.17.21", + "lodash": "^4.18.1", "lucene-query-parser": "1.2.0", "moment": "2.29.4", "ng-multiselect-dropdown": "^1.0.0", "ngx-json-viewer": "^3.2.1", "ngx-moment": "^6.0.2", "ngx-schema-form": "^2.14.1", + "pako": "^2.1.0", "primeng": "^20.3.0", "reflect-metadata": "0.1.13", "rxjs": "7.8.1", @@ -78,18 +79,17 @@ "zone.js": "~0.15.1" }, "devDependencies": { - "@angular-devkit/build-angular": "20.3.12", + "@angular-devkit/build-angular": "^20.3.25", "@angular-eslint/builder": "^20.0.0", "@angular-eslint/eslint-plugin": "^20.0.0", "@angular-eslint/eslint-plugin-template": "^20.0.0", "@angular-eslint/schematics": "^20.0.0", "@angular-eslint/template-parser": "^20.0.0", - "@angular/cli": "20.3.12", - "@angular/compiler-cli": "20.3.14", - "@angular/language-service": "20.3.14", + "@angular/cli": "^20.3.25", + "@angular/compiler-cli": "^20.3.19", + "@angular/language-service": "^20.3.19", "@types/estree": "0.0.51", "@types/jasmine": "3.10.3", - "@types/jasminewd2": "2.0.10", "@types/lodash": "4.14.178", "@types/node": "^22.19.1", "@types/sprintf-js": "^1.1.4", @@ -105,14 +105,13 @@ "husky": "7.0.4", "jasmine-core": "4.0.0", "jasmine-spec-reporter": "7.0.0", - "karma": "6.3.15", + "karma": "^6.4.4", "karma-chrome-launcher": "3.1.0", "karma-coverage-istanbul-reporter": "3.0.3", "karma-jasmine": "4.0.1", "karma-jasmine-html-reporter": "1.7.0", "mkdirp": "1.0.4", "ng-packagr": "20.3.2", - "protractor": "7.0.0", "raw-loader": "4.0.2", "rimraf": "3.0.2", "sass": "1.80.6", @@ -126,5 +125,8 @@ "hooks": { "pre-commit": "npm run ng lint" } + }, + "overrides": { + "braces": "^3.0.3" } } diff --git a/src/app/core/admin/user-management/user-management.component.ts b/src/app/core/admin/user-management/user-management.component.ts index fe2e4770a..5853ef503 100644 --- a/src/app/core/admin/user-management/user-management.component.ts +++ b/src/app/core/admin/user-management/user-management.component.ts @@ -11,7 +11,6 @@ import { UtilsService } from '@gsrs-core/utils'; import { DataSource } from '@angular/cdk/table'; import { FormControl } from '@angular/forms'; import {MatPaginator} from '@angular/material/paginator'; -import { TouchSequence } from 'selenium-webdriver'; @Component({ selector: 'app-user-management', diff --git a/src/app/core/bulkQuery/bulk-query.service.ts b/src/app/core/bulkQuery/bulk-query.service.ts index b88c01737..7586823c7 100644 --- a/src/app/core/bulkQuery/bulk-query.service.ts +++ b/src/app/core/bulkQuery/bulk-query.service.ts @@ -23,7 +23,6 @@ import { ValidationResults} from '@gsrs-core/substance-form/substance-form.model import {Facet, FacetQueryResponse} from '@gsrs-core/facets-manager'; import { StructuralUnit } from '@gsrs-core/substance'; import {HierarchyNode} from '@gsrs-core/substances-browse/substance-hierarchy/hierarchy.model'; -import { stringify } from 'querystring'; export class BulkQueryService extends BaseHttpService { constructor( diff --git a/src/app/core/structure-search/structure-search.component.ts b/src/app/core/structure-search/structure-search.component.ts index ab629deb7..2461adccb 100644 --- a/src/app/core/structure-search/structure-search.component.ts +++ b/src/app/core/structure-search/structure-search.component.ts @@ -20,7 +20,6 @@ import { OverlayContainer } from "@angular/cdk/overlay"; import { StructureExportComponent } from "@gsrs-core/structure/structure-export/structure-export.component"; import { Title } from "@angular/platform-browser"; import * as _ from "lodash"; -import { pipeline } from "stream"; import { take } from "rxjs"; import { StructureEditorComponent } from "@gsrs-core/structure-editor"; diff --git a/src/app/core/substance-details/substance-names/substance-names.component.ts b/src/app/core/substance-details/substance-names/substance-names.component.ts index 0ca63bd7a..b248dae70 100644 --- a/src/app/core/substance-details/substance-names/substance-names.component.ts +++ b/src/app/core/substance-details/substance-names/substance-names.component.ts @@ -10,7 +10,6 @@ import {Sort} from '@angular/material/sort'; import { OverlayContainer } from '@angular/cdk/overlay'; import {UtilsService} from '@gsrs-core/utils'; import { FormControl } from '@angular/forms'; -import { throws } from 'assert'; import { I } from '@angular/cdk/keycodes'; import { ConfigService } from '@gsrs-core/config'; diff --git a/src/app/core/substance-form/constituents/substance-form-constituents-card.component.ts b/src/app/core/substance-form/constituents/substance-form-constituents-card.component.ts index 6586238fa..c54e455ff 100644 --- a/src/app/core/substance-form/constituents/substance-form-constituents-card.component.ts +++ b/src/app/core/substance-form/constituents/substance-form-constituents-card.component.ts @@ -6,7 +6,6 @@ import {GoogleAnalyticsService} from '@gsrs-core/google-analytics'; import { SubstanceCardBaseFilteredList, SubstanceCardBaseList} from '@gsrs-core/substance-form/base-classes/substance-form-base-filtered-list'; import { SubstanceFormConstituentsService } from './substance-form-constituents.service'; import { MatCheckboxChange } from '@angular/material/checkbox'; -import { EventEmitter } from 'stream'; @Component({ selector: 'app-substance-form-constituents-card', diff --git a/src/app/core/substance/substance.service.ts b/src/app/core/substance/substance.service.ts index ebebbcbb2..80dcfc83a 100644 --- a/src/app/core/substance/substance.service.ts +++ b/src/app/core/substance/substance.service.ts @@ -26,7 +26,6 @@ import { StructuralUnit } from '@gsrs-core/substance'; import {HierarchyNode} from '@gsrs-core/substances-browse/substance-hierarchy/hierarchy.model'; import { SubstanceDependenciesImageNode } from '@gsrs-core/substance-details/substance-dependencies-image/substance-dependencies-image.model'; -import { stringify } from 'querystring'; import { AuthService } from "@gsrs-core/auth"; class CustomEncoder implements HttpParameterCodec { encodeKey(key: string): string { diff --git a/src/app/fda/application/application-details/application-darrts-details/application-darrts-details.component.ts b/src/app/fda/application/application-details/application-darrts-details/application-darrts-details.component.ts index 04f319b5b..1b7f8a8dd 100644 --- a/src/app/fda/application/application-details/application-darrts-details/application-darrts-details.component.ts +++ b/src/app/fda/application/application-details/application-darrts-details/application-darrts-details.component.ts @@ -9,7 +9,6 @@ import { GoogleAnalyticsService } from '@gsrs-core/google-analytics'; import { UtilsService } from '../../../../core/utils/utils.service'; // import { AuthService } from '@gsrs-core/auth/auth.service'; import { ApplicationDetailsBaseComponent } from '../application-details-base.component'; -import { element } from 'protractor'; @Component({ selector: 'app-application-darrts-details', diff --git a/src/app/fda/cross-entity-search/cross-entity-search.component.ts b/src/app/fda/cross-entity-search/cross-entity-search.component.ts index 0b5ea2300..e5dc0c972 100644 --- a/src/app/fda/cross-entity-search/cross-entity-search.component.ts +++ b/src/app/fda/cross-entity-search/cross-entity-search.component.ts @@ -23,7 +23,6 @@ import { AppNotification, NotificationType } from '@gsrs-core/main-notification/ import { Facet, FacetUpdateEvent } from '../../core/facets-manager/facet.model'; import { FacetParam } from '@gsrs-core/facets-manager'; import { DisplayFacet } from '@gsrs-core/facets-manager/display-facet'; -import { exitCode } from 'process'; @Component({ selector: 'app-cross-entity-search', diff --git a/src/app/fda/cross-entity-search/cross-entity-search.service.ts b/src/app/fda/cross-entity-search/cross-entity-search.service.ts index 370c2d993..01541a94e 100644 --- a/src/app/fda/cross-entity-search/cross-entity-search.service.ts +++ b/src/app/fda/cross-entity-search/cross-entity-search.service.ts @@ -25,7 +25,6 @@ import { StructuralUnit } from '@gsrs-core/substance'; import { HierarchyNode } from '@gsrs-core/substances-browse/substance-hierarchy/hierarchy.model'; import { SubstanceDependenciesImageNode } from '@gsrs-core/substance-details/substance-dependencies-image/substance-dependencies-image.model'; -import { stringify } from 'querystring'; class CustomEncoder implements HttpParameterCodec { encodeKey(key: string): string { return encodeURIComponent(key); diff --git a/src/app/fda/impurities/impurities-form/impurities-test-form/impurities-test-form.component.scss b/src/app/fda/impurities/impurities-form/impurities-test-form/impurities-test-form.component.scss index c935d6846..30d160088 100644 --- a/src/app/fda/impurities/impurities-form/impurities-test-form/impurities-test-form.component.scss +++ b/src/app/fda/impurities/impurities-form/impurities-test-form/impurities-test-form.component.scss @@ -529,7 +529,7 @@ width15percent { transform: translateY(-50%); } -.mat-form-field-style > { +.mat-form-field-style { /* OVERWRITE MATERIAL INPUT FIELDS */ .mat-form-field-infix { color: var(--regular-blue-color); diff --git a/src/app/fda/invitro-pharmacology/invitro-pharmacology-assay-data-import/invitro-pharmacology-assay-data-import.component.scss b/src/app/fda/invitro-pharmacology/invitro-pharmacology-assay-data-import/invitro-pharmacology-assay-data-import.component.scss index 9640d63e4..d631376ae 100644 --- a/src/app/fda/invitro-pharmacology/invitro-pharmacology-assay-data-import/invitro-pharmacology-assay-data-import.component.scss +++ b/src/app/fda/invitro-pharmacology/invitro-pharmacology-assay-data-import/invitro-pharmacology-assay-data-import.component.scss @@ -467,7 +467,7 @@ hr { background: var(--tabstyle-bg-img-end-color); } -.mat-form-field-style > { +.mat-form-field-style { /* OVERWRITE MATERIAL INPUT FIELDS */ .mat-form-field-infix { diff --git a/src/app/fda/invitro-pharmacology/invitro-pharmacology-form/invitro-pharmacology-assay-form/invitro-pharmacology-assay-form.component.scss b/src/app/fda/invitro-pharmacology/invitro-pharmacology-form/invitro-pharmacology-assay-form/invitro-pharmacology-assay-form.component.scss index cd6d2a4bd..9cdc19432 100644 --- a/src/app/fda/invitro-pharmacology/invitro-pharmacology-form/invitro-pharmacology-assay-form/invitro-pharmacology-assay-form.component.scss +++ b/src/app/fda/invitro-pharmacology/invitro-pharmacology-form/invitro-pharmacology-assay-form/invitro-pharmacology-assay-form.component.scss @@ -556,7 +556,7 @@ hr { max-width: 1140px; } -.mat-form-field-style > { +.mat-form-field-style { /* OVERWRITE MATERIAL INPUT FIELDS */ .mat-form-field-infix { color: var(--regular-blue-color); diff --git a/src/app/fda/invitro-pharmacology/invitro-pharmacology-form/invitro-pharmacology-assayset-form/invitro-pharmacology-assayset-form.component.scss b/src/app/fda/invitro-pharmacology/invitro-pharmacology-form/invitro-pharmacology-assayset-form/invitro-pharmacology-assayset-form.component.scss index 92f3c369e..c0afbf529 100644 --- a/src/app/fda/invitro-pharmacology/invitro-pharmacology-form/invitro-pharmacology-assayset-form/invitro-pharmacology-assayset-form.component.scss +++ b/src/app/fda/invitro-pharmacology/invitro-pharmacology-form/invitro-pharmacology-assayset-form/invitro-pharmacology-assayset-form.component.scss @@ -590,7 +590,7 @@ hr { max-width: 1140px; } -.mat-form-field-style > { +.mat-form-field-style { /* OVERWRITE MATERIAL INPUT FIELDS */ .mat-form-field-infix { color: var(--regular-blue-color); diff --git a/src/app/fda/invitro-pharmacology/invitro-pharmacology-form/invitro-pharmacology-form.component.scss b/src/app/fda/invitro-pharmacology/invitro-pharmacology-form/invitro-pharmacology-form.component.scss index f7a341cb2..8f397d388 100644 --- a/src/app/fda/invitro-pharmacology/invitro-pharmacology-form/invitro-pharmacology-form.component.scss +++ b/src/app/fda/invitro-pharmacology/invitro-pharmacology-form/invitro-pharmacology-form.component.scss @@ -686,7 +686,7 @@ hr { max-width: 1140px; } -.mat-form-field-style> { +.mat-form-field-style { /* OVERWRITE MATERIAL INPUT FIELDS */ .mat-form-field-infix { diff --git a/src/app/fda/invitro-pharmacology/invitro-pharmacology-form/invitro-pharmacology-summary-form/invitro-pharmacology-summary-form.component.scss b/src/app/fda/invitro-pharmacology/invitro-pharmacology-form/invitro-pharmacology-summary-form/invitro-pharmacology-summary-form.component.scss index 19801c057..2d5b70503 100644 --- a/src/app/fda/invitro-pharmacology/invitro-pharmacology-form/invitro-pharmacology-summary-form/invitro-pharmacology-summary-form.component.scss +++ b/src/app/fda/invitro-pharmacology/invitro-pharmacology-form/invitro-pharmacology-summary-form/invitro-pharmacology-summary-form.component.scss @@ -652,7 +652,7 @@ hr { max-width: 1140px; } -.mat-form-field-style > { +.mat-form-field-style { /* OVERWRITE MATERIAL INPUT FIELDS */ .mat-form-field-infix { color: var(--regular-blue-color); diff --git a/src/app/fda/invitro-pharmacology/invitro-pharmacology-screening-data-import/invitro-pharmacology-screening-data-import.component.scss b/src/app/fda/invitro-pharmacology/invitro-pharmacology-screening-data-import/invitro-pharmacology-screening-data-import.component.scss index a4bb0bc22..c3b2de975 100644 --- a/src/app/fda/invitro-pharmacology/invitro-pharmacology-screening-data-import/invitro-pharmacology-screening-data-import.component.scss +++ b/src/app/fda/invitro-pharmacology/invitro-pharmacology-screening-data-import/invitro-pharmacology-screening-data-import.component.scss @@ -530,7 +530,7 @@ hr { vertical-align: middle; } -.mat-form-field-style > { +.mat-form-field-style { /* OVERWRITE MATERIAL INPUT FIELDS */ .mat-form-field-infix { diff --git a/src/app/fda/invitro-pharmacology/invitro-pharmacology.component.scss b/src/app/fda/invitro-pharmacology/invitro-pharmacology.component.scss index ad6358d4a..796708e5e 100644 --- a/src/app/fda/invitro-pharmacology/invitro-pharmacology.component.scss +++ b/src/app/fda/invitro-pharmacology/invitro-pharmacology.component.scss @@ -301,7 +301,7 @@ width: 300px; } -.mat-form-field-style > { +.mat-form-field-style { /* OVERWRITE MATERIAL INPUT FIELDS */ .mat-form-field-infix { From 33f764e7df6e44b0b19b9a42988b6b9e55fca008 Mon Sep 17 00:00:00 2001 From: Newatia Date: Tue, 5 May 2026 13:55:43 -0400 Subject: [PATCH 386/408] fixed application and adverse events in Substance Details --- src/app/fda/config/config.json | 6 +-- .../substance-adverseeventcvm.component.ts | 26 ++++++++--- .../substance-adverseeventdme.component.ts | 36 ++++++++++----- .../substance-adverseeventpt.component.html | 9 ++-- .../substance-adverseeventpt.component.scss | 17 ++++++- .../substance-adverseeventpt.component.ts | 40 ++++++++++++----- .../substance-application.component.ts | 45 +++++++++++++------ .../substance-details-base-table-display.ts | 9 +++- 8 files changed, 138 insertions(+), 50 deletions(-) diff --git a/src/app/fda/config/config.json b/src/app/fda/config/config.json index 021bd58a4..c5afe7475 100644 --- a/src/app/fda/config/config.json +++ b/src/app/fda/config/config.json @@ -2,9 +2,9 @@ "version": "3.2.0-SNAPSHOT", "contactEmail": "GSRSSupport@fda.hhs.gov", "displayMatchApplication": "true", - "adverseEventShinyHomepageDisplay": "true", - "adverseEventShinySubstanceNameDisplay": "true", - "adverseEventShinyAdverseEventDisplay": "true", + "adverseEventShinyHomepageDisplay": "false", + "adverseEventShinySubstanceNameDisplay": "false", + "adverseEventShinyAdverseEventDisplay": "false", "FAERSDashboardAdverseEventUrl": "https://fis.fda.gov/sense/app/95239e26-e0be-42d9-a960-9a5f7f1c25ee/sheet/45beeb74-30ab-46be-8267-5756582633b4/state/analysis", "dailyMedUrl": "https://dailymed.nlm.nih.gov/dailymed/search.cfm?labeltype=all&query=", "phpIdUrl": "https://phpidpublish.who-umc.org/search?phpid=", diff --git a/src/app/fda/substance-details/substance-products/substance-adverseevent/adverseeventcvm/substance-adverseeventcvm.component.ts b/src/app/fda/substance-details/substance-products/substance-adverseevent/adverseeventcvm/substance-adverseeventcvm.component.ts index ff39e23e6..a048a0ba9 100644 --- a/src/app/fda/substance-details/substance-products/substance-adverseevent/adverseeventcvm/substance-adverseeventcvm.component.ts +++ b/src/app/fda/substance-details/substance-products/substance-adverseevent/adverseeventcvm/substance-adverseeventcvm.component.ts @@ -1,4 +1,4 @@ -import { Component, OnInit, Output, EventEmitter } from "@angular/core"; +import { Component, OnInit, Output, Input, EventEmitter } from "@angular/core"; import { ActivatedRoute, Router, NavigationExtras } from "@angular/router"; import { MatDialog } from "@angular/material/dialog"; import { Sort } from "@angular/material/sort"; @@ -22,10 +22,10 @@ import { adverseEventCvmSearchSortValues } from "../../../../adverse-event/adver }) export class SubstanceAdverseEventCvmComponent extends SubstanceDetailsBaseTableDisplay - implements OnInit -{ + implements OnInit { @Output() countAdvCvmOut: EventEmitter = new EventEmitter(); + localBdnum: string; adverseEventCount = 0; order = "$root_aeCount"; ascDescDir = "desc"; @@ -60,11 +60,14 @@ export class SubstanceAdverseEventCvmComponent async ngOnInit() { this.canExport = await this.authService.hasSpecificPrivilege("Export Data"); + /* Commenting right now. Will remove later after everything works */ + /* if (this.bdnum) { this.getAdverseEventCvm(); // this.getSubstanceAdverseEventCvm(); this.adverseEventCvmListExportUrl(); } + */ } ngOnDestroy(): void { @@ -73,11 +76,22 @@ export class SubstanceAdverseEventCvmComponent }); } + @Input() + set bdnum(setBdnum: string) { + this.localBdnum = setBdnum; + + if (this.localBdnum) { + this.getAdverseEventCvm(); + + this.adverseEventCvmListExportUrl(); + } + } + getAdverseEventCvm(pageEvent?: PageEvent) { this.setPageEvent(pageEvent); this.showSpinner = true; // Start progress spinner const skip = this.page * this.pageSize; - const privateSearch = "root_substanceKey:" + this.bdnum; + const privateSearch = "root_substanceKey:" + this.localBdnum; const subscription = this.adverseEventService .getAdverseEventCvm( this.order, @@ -176,9 +190,9 @@ export class SubstanceAdverseEventCvmComponent } adverseEventCvmListExportUrl() { - if (this.bdnum != null) { + if (this.localBdnum != null) { this.exportUrl = this.adverseEventService.getAdverseEventCvmListExportUrl( - this.bdnum + this.localBdnum ); } } diff --git a/src/app/fda/substance-details/substance-products/substance-adverseevent/adverseeventdme/substance-adverseeventdme.component.ts b/src/app/fda/substance-details/substance-products/substance-adverseevent/adverseeventdme/substance-adverseeventdme.component.ts index 63ff048e4..85f2d4049 100644 --- a/src/app/fda/substance-details/substance-products/substance-adverseevent/adverseeventdme/substance-adverseeventdme.component.ts +++ b/src/app/fda/substance-details/substance-products/substance-adverseevent/adverseeventdme/substance-adverseeventdme.component.ts @@ -15,16 +15,17 @@ import { Subscription } from 'rxjs'; import { adverseEventDmeSearchSortValues } from '../../../../adverse-event/adverse-events-dme-browse/adverse-events-dme-search-sort-values'; @Component({ - selector: 'app-substance-adverseeventdme', - templateUrl: './substance-adverseeventdme.component.html', - styleUrls: ['./substance-adverseeventdme.component.scss'], - standalone: false + selector: 'app-substance-adverseeventdme', + templateUrl: './substance-adverseeventdme.component.html', + styleUrls: ['./substance-adverseeventdme.component.scss'], + standalone: false }) export class SubstanceAdverseEventDmeComponent extends SubstanceDetailsBaseTableDisplay implements OnInit { @Output() countAdvDmeOut: EventEmitter = new EventEmitter(); + localBdnum: string; adverseEventCount = 0; order = '$root_dmeCount'; ascDescDir = 'desc'; @@ -56,11 +57,15 @@ export class SubstanceAdverseEventDmeComponent extends SubstanceDetailsBaseTable async ngOnInit() { this.canExport = await this.authService.hasSpecificPrivilege('Export Data'); + + /* Commenting right now. Will remove later after everything works */ + /* if (this.bdnum) { this.getAdverseEventDme(); // this.getSubstanceAdverseEventDme(); this.adverseEventDmeListExportUrl(); } + */ } ngOnDestroy(): void { @@ -69,11 +74,22 @@ export class SubstanceAdverseEventDmeComponent extends SubstanceDetailsBaseTable }); } + @Input() + set bdnum(setBdnum: string) { + this.localBdnum = setBdnum; + + if (this.localBdnum) { + this.getAdverseEventDme(); + + this.adverseEventDmeListExportUrl(); + } + } + getAdverseEventDme(pageEvent?: PageEvent) { this.setPageEvent(pageEvent); this.showSpinner = true; // Start progress spinner const skip = this.page * this.pageSize; - const privateSearch = 'root_substanceKey:' + this.bdnum; + const privateSearch = 'root_substanceKey:' + this.localBdnum; const subscription = this.adverseEventService.getAdverseEventDme( this.order, skip, @@ -134,9 +150,9 @@ export class SubstanceAdverseEventDmeComponent extends SubstanceDetailsBaseTable const url = this.getApiExportUrl(this.etag, extension); if (this.authService.getUser() !== '') { const dialogReference = this.dialog.open(ExportDialogComponent, { - // height: '215x', + // height: '215x', width: '700px', - data: { 'extension': extension, 'type': 'substanceAdverseEventDme','entity': 'adverseeventdme', 'hideOptionButtons': true } + data: { 'extension': extension, 'type': 'substanceAdverseEventDme', 'entity': 'adverseeventdme', 'hideOptionButtons': true } }); // this.overlayContainer.style.zIndex = '1002'; dialogReference.afterClosed().subscribe(response => { @@ -147,7 +163,7 @@ export class SubstanceAdverseEventDmeComponent extends SubstanceDetailsBaseTable this.loadingService.setLoading(true); const fullname = name + '.' + extension; this.authService.startUserDownload(url, this.privateExport, fullname, id).subscribe(response => { - // this.authService.startUserDownload(url, this.privateExport, fullname).subscribe(response => { + // this.authService.startUserDownload(url, this.privateExport, fullname).subscribe(response => { this.loadingService.setLoading(false); const navigationExtras: NavigationExtras = { queryParams: { @@ -168,8 +184,8 @@ export class SubstanceAdverseEventDmeComponent extends SubstanceDetailsBaseTable } adverseEventDmeListExportUrl() { - if (this.bdnum != null) { - this.exportUrl = this.adverseEventService.getAdverseEventDmeListExportUrl(this.bdnum); + if (this.localBdnum != null) { + this.exportUrl = this.adverseEventService.getAdverseEventDmeListExportUrl(this.localBdnum); } } diff --git a/src/app/fda/substance-details/substance-products/substance-adverseevent/adverseeventpt/substance-adverseeventpt.component.html b/src/app/fda/substance-details/substance-products/substance-adverseevent/adverseeventpt/substance-adverseeventpt.component.html index 5f6b50c7d..7774285a4 100644 --- a/src/app/fda/substance-details/substance-products/substance-adverseevent/adverseeventpt/substance-adverseeventpt.component.html +++ b/src/app/fda/substance-details/substance-products/substance-adverseevent/adverseeventpt/substance-adverseeventpt.component.html @@ -4,7 +4,7 @@
    Adverse Event PT
    -
    +
    +
    -
    + diff --git a/src/app/fda/substance-details/substance-products/substance-adverseevent/adverseeventpt/substance-adverseeventpt.component.scss b/src/app/fda/substance-details/substance-products/substance-adverseevent/adverseeventpt/substance-adverseeventpt.component.scss index 60530fedb..d5c021e7e 100644 --- a/src/app/fda/substance-details/substance-products/substance-adverseevent/adverseeventpt/substance-adverseeventpt.component.scss +++ b/src/app/fda/substance-details/substance-products/substance-adverseevent/adverseeventpt/substance-adverseeventpt.component.scss @@ -18,10 +18,18 @@ font-size: 10px; } +.font14px { + font-size: 14px; +} + .font15px { font-size: 15px; } +.margintopneg10px { + margin-top: -10px; +} + .margintop10px { margin-top: 10px; } @@ -70,8 +78,8 @@ marginleft50px { border: 1px solid var(--regular-grey-color); } -.width120px { - width: 120px; +.width140px { + width: 140px; display: block; } @@ -80,6 +88,11 @@ marginleft50px { display: block; } +.width260px { + width: 260px; + display: block; +} + .spinnerstyle { position: absolute; top: 0; diff --git a/src/app/fda/substance-details/substance-products/substance-adverseevent/adverseeventpt/substance-adverseeventpt.component.ts b/src/app/fda/substance-details/substance-products/substance-adverseevent/adverseeventpt/substance-adverseeventpt.component.ts index 9b81ddf1f..5c627cfc5 100644 --- a/src/app/fda/substance-details/substance-products/substance-adverseevent/adverseeventpt/substance-adverseeventpt.component.ts +++ b/src/app/fda/substance-details/substance-products/substance-adverseevent/adverseeventpt/substance-adverseeventpt.component.ts @@ -16,16 +16,17 @@ import { Subscription } from 'rxjs'; import { adverseEventPtSearchSortValues } from '../../../../adverse-event/adverse-events-pt-browse/adverse-events-pt-search-sort-values'; @Component({ - selector: 'app-substance-adverseeventpt', - templateUrl: './substance-adverseeventpt.component.html', - styleUrls: ['./substance-adverseeventpt.component.scss'], - standalone: false + selector: 'app-substance-adverseeventpt', + templateUrl: './substance-adverseeventpt.component.html', + styleUrls: ['./substance-adverseeventpt.component.scss'], + standalone: false }) export class SubstanceAdverseEventPtComponent extends SubstanceDetailsBaseTableDisplay implements OnInit, OnDestroy { @Input() substanceName: string; @Output() countAdvPtOut: EventEmitter = new EventEmitter(); + localBdnum: string; adverseEventCount = 0; order = '$root_ptCount'; ascDescDir = 'desc'; @@ -61,7 +62,7 @@ export class SubstanceAdverseEventPtComponent extends SubstanceDetailsBaseTableD 'prr' ]; - canExport:boolean = false; + canExport: boolean = false; constructor( private router: Router, @@ -78,6 +79,8 @@ export class SubstanceAdverseEventPtComponent extends SubstanceDetailsBaseTableD async ngOnInit() { this.canExport = await this.authService.hasSpecificPrivilege('Export Data'); + /* Commenting right now. Will remove later after everything works */ + /* if (this.bdnum) { this.getAdverseEventPt(); @@ -88,6 +91,7 @@ export class SubstanceAdverseEventPtComponent extends SubstanceDetailsBaseTableD this.adverseEventPtListExportUrl(); this.getAdverseEventShinyConfig(); } + */ } ngOnDestroy(): void { @@ -96,11 +100,27 @@ export class SubstanceAdverseEventPtComponent extends SubstanceDetailsBaseTableD }); } + @Input() + set bdnum(setBdnum: string) { + this.localBdnum = setBdnum; + + if (this.localBdnum) { + this.getAdverseEventPt(); + + // FAERS DASHBOARD + this.getFaersDashboardUrl(); + this.getFaersDashboardRecordByName(); + + this.adverseEventPtListExportUrl(); + this.getAdverseEventShinyConfig(); + } + } + getAdverseEventPt(pageEvent?: PageEvent) { this.setPageEvent(pageEvent); this.showSpinner = true; // Start progress spinner const skip = this.page * this.pageSize; - const privateSearch = 'root_substanceKey:' + this.bdnum; + const privateSearch = 'root_substanceKey:' + this.localBdnum; const subscription = this.adverseEventService.getAdverseEventPt( this.order, skip, @@ -140,8 +160,8 @@ export class SubstanceAdverseEventPtComponent extends SubstanceDetailsBaseTableD */ adverseEventPtListExportUrl() { - if (this.bdnum != null) { - this.exportUrl = this.adverseEventService.getAdverseEventPtListExportUrl(this.bdnum); + if (this.localBdnum != null) { + this.exportUrl = this.adverseEventService.getAdverseEventPtListExportUrl(this.localBdnum); } } @@ -167,7 +187,7 @@ export class SubstanceAdverseEventPtComponent extends SubstanceDetailsBaseTableD const url = this.getApiExportUrl(this.etag, extension); if (this.authService.getUser() !== '') { const dialogReference = this.dialog.open(ExportDialogComponent, { - // height: '215x', + // height: '215x', width: '700px', data: { 'extension': extension, 'type': 'substanceAdverseEventPt', 'entity': 'adverseeventpt', 'hideOptionButtons': true } }); @@ -180,7 +200,7 @@ export class SubstanceAdverseEventPtComponent extends SubstanceDetailsBaseTableD this.loadingService.setLoading(true); const fullname = name + '.' + extension; this.authService.startUserDownload(url, this.privateExport, fullname, id).subscribe(response => { - // this.authService.startUserDownload(url, this.privateExport, fullname).subscribe(response => { + // this.authService.startUserDownload(url, this.privateExport, fullname).subscribe(response => { this.loadingService.setLoading(false); const navigationExtras: NavigationExtras = { queryParams: { diff --git a/src/app/fda/substance-details/substance-products/substance-application/substance-application.component.ts b/src/app/fda/substance-details/substance-products/substance-application/substance-application.component.ts index ae1d0deb3..1dff69c77 100644 --- a/src/app/fda/substance-details/substance-products/substance-application/substance-application.component.ts +++ b/src/app/fda/substance-details/substance-products/substance-application/substance-application.component.ts @@ -17,13 +17,14 @@ import { SubstanceCardBaseFilteredList } from '@gsrs-core/substance-details'; import { applicationSearchSortValues } from '../../../application/applications-browse/application-search-sort-values'; @Component({ - selector: 'app-substance-application', - templateUrl: './substance-application.component.html', - styleUrls: ['./substance-application.component.scss'], - standalone: false + selector: 'app-substance-application', + templateUrl: './substance-application.component.html', + styleUrls: ['./substance-application.component.scss'], + standalone: false }) export class SubstanceApplicationComponent extends SubstanceDetailsBaseTableDisplay implements OnInit { + localBdnum: string; application: any; applicationCount = 0; totalApplication = 0; @@ -73,6 +74,8 @@ export class SubstanceApplicationComponent extends SubstanceDetailsBaseTableDisp this.canExport = await this.authService.hasSpecificPrivilege('Export Data'); this.canUpdate = await this.authService.hasSpecificPrivilege('Edit') + /* Commenting right now. Will remove later after everything works */ + /* if (this.bdnum) { this.getApplicationCenterList(); @@ -80,10 +83,24 @@ export class SubstanceApplicationComponent extends SubstanceDetailsBaseTableDisp + this.bdnum; this.getApplicationBySubstanceKeyCenter(null, 'initial'); } + */ + } + + @Input() + set bdnum(setBdnum: string) { + this.localBdnum = setBdnum; + + if (this.localBdnum) { + this.getApplicationCenterList(); + + this.privateSearch = 'root_applicationProductList_applicationIngredientList_substanceKey:' + + this.localBdnum; + this.getApplicationBySubstanceKeyCenter(null, 'initial'); + } } getApplicationCenterList(): void { - this.applicationService.getApplicationCenterList(this.bdnum).subscribe(results => { + this.applicationService.getApplicationCenterList(this.localBdnum).subscribe(results => { this.centerListOriginal = results; this.centerList = results; if (this.centerList && this.centerList.length > 0) { @@ -113,29 +130,29 @@ export class SubstanceApplicationComponent extends SubstanceDetailsBaseTableDisp if ($event) { const evt: any = $event.tab; const textLabel: string = evt.textLabel; - + // Extract center and table information from the tab label if (textLabel != null) { this.loadingStatus = 'Loading data...'; - + // Parse the tab label to extract center (before space) and fromTable (after space) const index = textLabel.indexOf(' '); this.center = textLabel.slice(0, index); this.fromTable = textLabel.slice(index + 1, textLabel.length); } - + // Clear existing results before loading new data this.paged = []; - + // Build search query with substance key, center, and source table parameters this.privateSearch = 'root_applicationProductList_applicationIngredientList_substanceKey:' - + this.bdnum + ' AND root_center:' + this.center + ' AND root_fromTable: ' + this.fromTable; - + + this.localBdnum + ' AND root_center:' + this.center + ' AND root_fromTable: ' + this.fromTable; + // Fetch application data based on the constructed search criteria this.getApplicationBySubstanceKeyCenter(); } } - + // GSRS 3.0 getApplicationBySubstanceKeyCenter(pageEvent?: PageEvent, searchType?: string) { this.setPageEvent(pageEvent); @@ -245,8 +262,8 @@ export class SubstanceApplicationComponent extends SubstanceDetailsBaseTableDisp } applicationListExportUrl() { - if (this.bdnum != null) { - this.exportUrl = this.applicationService.getApplicationListExportUrl(this.bdnum); + if (this.localBdnum != null) { + this.exportUrl = this.applicationService.getApplicationListExportUrl(this.localBdnum); } } diff --git a/src/app/fda/substance-details/substance-products/substance-details-base-table-display.ts b/src/app/fda/substance-details/substance-products/substance-details-base-table-display.ts index 9758a9c15..b2681441d 100644 --- a/src/app/fda/substance-details/substance-products/substance-details-base-table-display.ts +++ b/src/app/fda/substance-details/substance-products/substance-details-base-table-display.ts @@ -14,7 +14,14 @@ export class SubstanceDetailsBaseTableDisplay public results: Array = []; exportUrl: string; - @Input() bdnum: string; + @Input() + private _bdnum: string; + public get bdnum(): string { + return this._bdnum; + } + public set bdnum(value: string) { + this._bdnum = value; + } constructor( public gaService: GoogleAnalyticsService, From cc5b165a4734677aadf867274f0c94b4c834e2c2 Mon Sep 17 00:00:00 2001 From: Iaroslav Moskviak Date: Mon, 11 May 2026 11:52:13 -0400 Subject: [PATCH 387/408] jsdraw resize, textarea and molfile fixes --- .../jsdraw-wrapper.component.ts | 127 +++++++++++++++--- .../structure-editor-implementation.model.ts | 22 ++- .../structure-editor.component.html | 2 +- .../structure-editor.component.ts | 6 +- .../names/name-form.component.html | 1 + .../names/name-form.component.ts | 119 +++++++++------- ...ubstance-form-change-reason.component.html | 2 +- 7 files changed, 196 insertions(+), 83 deletions(-) diff --git a/src/app/core/structure-editor/jsdraw-wrapper/jsdraw-wrapper.component.ts b/src/app/core/structure-editor/jsdraw-wrapper/jsdraw-wrapper.component.ts index 56b8285d8..ab14f1f1b 100644 --- a/src/app/core/structure-editor/jsdraw-wrapper/jsdraw-wrapper.component.ts +++ b/src/app/core/structure-editor/jsdraw-wrapper/jsdraw-wrapper.component.ts @@ -1,46 +1,137 @@ -import { Component, AfterViewInit, Output, EventEmitter, PLATFORM_ID, Inject } from '@angular/core'; -import { isPlatformBrowser } from '@angular/common'; -import { JSDraw } from '@gsrs-core/structure-editor'; +import { + Component, + AfterViewInit, + Output, + EventEmitter, + PLATFORM_ID, + Inject, + ElementRef, + DestroyRef, + HostListener, +} from "@angular/core"; +import { isPlatformBrowser } from "@angular/common"; +import { JSDraw } from "@gsrs-core/structure-editor"; @Component({ - selector: 'ncats-jsdraw-wrapper', - template: `
    `, - styles: [], - standalone: false + selector: "ncats-jsdraw-wrapper", + template: `
    `, + styles: [":host { display: block; } :host([hidden]) { display: none; }"], + standalone: false, }) export class JsdrawWrapperComponent implements AfterViewInit { randomId: string; private jsdraw: JSDraw; @Output() jsDrawOnLoad = new EventEmitter(); + private resizeFrame?: number; + private resizeObserver?: ResizeObserver; + private loadAttempts = 0; + private lastDrawingWidth = 0; + private lastHeight = 0; - constructor(@Inject(PLATFORM_ID) private platformId: Object) { - this.randomId = Math.random().toString(36).replace('0.', ''); + constructor( + @Inject(PLATFORM_ID) private platformId: Object, + private el: ElementRef, + private destroyRef: DestroyRef + ) { + this.randomId = Math.random().toString(36).replace("0.", ""); + this.destroyRef.onDestroy(() => { + if (this.resizeFrame != null && isPlatformBrowser(this.platformId)) { + window.cancelAnimationFrame(this.resizeFrame); + } + this.resizeObserver?.disconnect(); + }); } ngAfterViewInit() { this.loadEditor(); + + if (isPlatformBrowser(this.platformId)) { + if ("ResizeObserver" in window) { + this.resizeObserver = new ResizeObserver(() => this.scheduleResize()); + this.resizeObserver.observe(this.el.nativeElement); + } + } } - loadEditor(): void { - let count = 0; + @HostListener("window:resize") + onWindowResize(): void { + if (!this.resizeObserver) { + this.scheduleResize(); + } + } + + private scheduleResize(): void { + if (!isPlatformBrowser(this.platformId)) { return; } + if (this.resizeFrame != null) { return; } + this.resizeFrame = window.requestAnimationFrame(() => { + this.resizeFrame = undefined; + this.doResize(); + }); + } + + loadEditor(): void { if (isPlatformBrowser(this.platformId)) { + const win = window as any; //this will ensure that the extra resources file has been loaded before the full editor is - if (window['JSDraw'] && window['dojo'] && window['scil'] && window['scil'].Utils && window['scil'].Utils._loadedAdditions) { - window['dojo'].ready(() => { - this.jsdraw = new window['JSDraw'](this.randomId); + if ( + win["JSDraw"] && + win["dojo"] && + win["scil"] && + win["scil"].Utils && + win["scil"].Utils._loadedAdditions + ) { + win["dojo"].ready(() => { + this.jsdraw = new win["JSDraw"](this.randomId); this.jsDrawOnLoad.emit(this.jsdraw); + this.scheduleResize(); //customization of buttons - if(window['afterSketcherMade']){ - window['afterSketcherMade'](); + if (win["afterSketcherMade"]) { + win["afterSketcherMade"](); } }); - } else if (count < 5000) { - count++; + } else if (this.loadAttempts < 5000) { + this.loadAttempts++; setTimeout(() => { this.loadEditor(); }, 10); } } } + + private doResize(): void { + const editor = this.jsdraw as any; + // Skip if not initialised, hidden (display:none), or resize API absent + if (!editor || !this.el.nativeElement.offsetParent || typeof editor.resize !== "function") { return; } + + const availableWidth = Math.floor(this.el.nativeElement.getBoundingClientRect().width); + if (availableWidth <= 0) { return; } + + const svg = this.el.nativeElement.querySelector("svg") as SVGSVGElement | null; + const height = this.getSvgDimension(svg, "height"); + if (height <= 0) { return; } + + const editorRoot = this.el.nativeElement.firstElementChild as HTMLElement | null; + const svgWidth = this.getSvgDimension(svg, "width"); + const editorWidth = Math.floor(editorRoot?.getBoundingClientRect().width ?? 0) || availableWidth; + const chromeWidth = svgWidth > 0 ? Math.max(0, editorWidth - svgWidth) : 0; + const drawingWidth = Math.max(1, availableWidth - chromeWidth); + if (drawingWidth === this.lastDrawingWidth && height === this.lastHeight) { return; } + + editor.resize(drawingWidth, height); + this.lastDrawingWidth = drawingWidth; + this.lastHeight = height; + } + + private getSvgDimension(svg: SVGSVGElement | null, dimension: "width" | "height"): number { + if (!svg) { return 0; } + + const baseValue = svg[dimension]?.baseVal?.value; + if (baseValue > 0) { return baseValue; } + + const attributeValue = parseInt(svg.getAttribute(dimension) ?? "0", 10); + if (attributeValue > 0) { return attributeValue; } + + return Math.floor(svg.getBoundingClientRect()[dimension]); + } } diff --git a/src/app/core/structure-editor/structure-editor-implementation.model.ts b/src/app/core/structure-editor/structure-editor-implementation.model.ts index bfec96e44..d7abf6ece 100644 --- a/src/app/core/structure-editor/structure-editor-implementation.model.ts +++ b/src/app/core/structure-editor/structure-editor-implementation.model.ts @@ -55,11 +55,9 @@ export class EditorImplementation implements Editor { getMolfile(): Observable { return new Observable(observer => { if (this.ketcher && this.ketcher != null) { - this.ketcher.getMolfile('v2000').then(result => { - let mfile = result; - - observer.next(mfile); - + this.ketcher.getMolfile('v2000').then(result => { + observer.next(result); + }); @@ -155,25 +153,25 @@ export class EditorImplementation implements Editor { return new Observable(observer => { if (this.jsdraw != null) { this.jsdraw.options.ondatachange = () => { - this.getMolfile().pipe(take(1)).subscribe(result => { + this.getMolfile().pipe(take(1)).subscribe(result => { observer.next(result); }); }; } else if (this.ketcher != null) { - this.ketcher.editor.subscribe('change', operations => { + this.ketcher.editor.subscribe('change', operations => { if(!(operations.length == 1 && operations[0].operation == 'Load canvas')){ - this.ketcher.getMolfile('v2000').then(result => { + this.ketcher.getMolfile('v2000').then(result => { observer.next(result); }); } else { - this.getMolfile().pipe(take(1)).subscribe(result => { + this.getMolfile().pipe(take(1)).subscribe(result => { observer.next(result); }); } - - + + }); - + } else { observer.next(null); diff --git a/src/app/core/structure-editor/structure-editor.component.html b/src/app/core/structure-editor/structure-editor.component.html index e822d9ba4..f9c730cb4 100644 --- a/src/app/core/structure-editor/structure-editor.component.html +++ b/src/app/core/structure-editor/structure-editor.component.html @@ -26,7 +26,7 @@ oncontextmenu="return false;" (jsDrawOnLoad)="jsDrawOnLoad($event)" [hidden]="structureEditor === 'ketcher' && !firstload" - style="width: 990px" + style="width: 100%" >
    diff --git a/src/app/core/structure-editor/structure-editor.component.ts b/src/app/core/structure-editor/structure-editor.component.ts index 2106528b8..0f4e7a80b 100644 --- a/src/app/core/structure-editor/structure-editor.component.ts +++ b/src/app/core/structure-editor/structure-editor.component.ts @@ -504,10 +504,8 @@ export class StructureEditorComponent implements OnInit, AfterViewInit, OnDestro if (this.enableJSDraw) { this.ketcher.editor.event.change.handlers.push({ f: (c) => { - this.ketcher.getMolfile().then(result => { - let mfile = [null]; - mfile[0] = result; - this.getSketcher().setFile(mfile[0], "mol"); + this.ketcher.getMolfile('v2000').then((result: string) => { + this.getSketcher().setFile(result, "mol"); }) } }); diff --git a/src/app/core/substance-form/names/name-form.component.html b/src/app/core/substance-form/names/name-form.component.html index 72583f764..3aae1d5df 100644 --- a/src/app/core/substance-form/names/name-form.component.html +++ b/src/app/core/substance-form/names/name-form.component.html @@ -41,6 +41,7 @@ required name="name" (keypress)="preventNewLine($event)" + (input)="autoResize($event)" > diff --git a/src/app/core/substance-form/names/name-form.component.ts b/src/app/core/substance-form/names/name-form.component.ts index 174713b27..2739229e7 100644 --- a/src/app/core/substance-form/names/name-form.component.ts +++ b/src/app/core/substance-form/names/name-form.component.ts @@ -1,33 +1,44 @@ -import { Component, OnInit, Input, Output, EventEmitter, OnDestroy } from '@angular/core'; -import { SubstanceDetail, SubstanceName, SubstanceNameOrg } from '../../substance/substance.model'; -import { ControlledVocabularyService } from '../../controlled-vocabulary/controlled-vocabulary.service'; -import { FormControl, Validators } from '@angular/forms'; -import { MatRadioChange } from '@angular/material/radio'; -import { UtilsService } from '../../utils/utils.service'; -import { Subscription } from 'rxjs'; -import {NameResolverDialogComponent} from '@gsrs-core/name-resolver/name-resolver-dialog.component'; -import {OverlayContainer} from '@angular/cdk/overlay'; -import {MatDialog} from '@angular/material/dialog'; -import {SubstanceFormService} from '@gsrs-core/substance-form/substance-form.service'; -import { SubstanceFormNamesService } from '@gsrs-core/substance-form/names/substance-form-names.service'; -import { AuthService } from '@gsrs-core/auth'; +import { + Component, + OnInit, + Input, + Output, + EventEmitter, + OnDestroy, +} from "@angular/core"; +import { + SubstanceDetail, + SubstanceName, + SubstanceNameOrg, +} from "../../substance/substance.model"; +import { ControlledVocabularyService } from "../../controlled-vocabulary/controlled-vocabulary.service"; +import { FormControl, Validators } from "@angular/forms"; +import { MatRadioChange } from "@angular/material/radio"; +import { UtilsService } from "../../utils/utils.service"; +import { Subscription } from "rxjs"; +import { NameResolverDialogComponent } from "@gsrs-core/name-resolver/name-resolver-dialog.component"; +import { OverlayContainer } from "@angular/cdk/overlay"; +import { MatDialog } from "@angular/material/dialog"; +import { SubstanceFormService } from "@gsrs-core/substance-form/substance-form.service"; +import { SubstanceFormNamesService } from "@gsrs-core/substance-form/names/substance-form-names.service"; +import { AuthService } from "@gsrs-core/auth"; @Component({ - selector: 'app-name-form', - templateUrl: './name-form.component.html', - styleUrls: ['./name-form.component.scss'], - standalone: false + selector: "app-name-form", + templateUrl: "./name-form.component.html", + styleUrls: ["./name-form.component.scss"], + standalone: false, }) export class NameFormComponent implements OnInit, OnDestroy { private privateName: SubstanceName; @Output() priorityUpdate = new EventEmitter(); @Output() nameDeleted = new EventEmitter(); - nameControl = new FormControl(''); - nameTypeControl = new FormControl(''); + nameControl = new FormControl(""); + nameTypeControl = new FormControl(""); deleteTimer: any; private subscriptions: Array = []; overlayContainer: HTMLElement; - substanceType = ''; + substanceType = ""; viewFull = true; showStd = false; canChangeDisplayName: boolean = false; @@ -41,22 +52,26 @@ export class NameFormComponent implements OnInit, OnDestroy { private overlayContainerService: OverlayContainer, private nameFormService: SubstanceFormNamesService, private authService: AuthService, - ) { } + ) {} async ngOnInit() { this.overlayContainer = this.overlayContainerService.getContainerElement(); - const definition = this.substanceFormService.definition.subscribe(def => { + const definition = this.substanceFormService.definition.subscribe((def) => { this.substanceType = def.substanceClass; }); definition.unsubscribe(); - - this.canChangeDisplayName = await this.authService.hasSpecificPrivilege("Change Display Name for Approved"); - - this.substanceStatus = this.substanceFormService.getSubstanceStatus().toUpperCase(); + + this.canChangeDisplayName = await this.authService.hasSpecificPrivilege( + "Change Display Name for Approved", + ); + + this.substanceStatus = this.substanceFormService + .getSubstanceStatus() + .toUpperCase(); } ngOnDestroy() { - this.subscriptions.forEach(subscription => { + this.subscriptions.forEach((subscription) => { subscription.unsubscribe(); }); } @@ -64,7 +79,7 @@ export class NameFormComponent implements OnInit, OnDestroy { @Input() set show(val: boolean) { if (val != null) { - this.viewFull = val; + this.viewFull = val; } } @@ -75,7 +90,7 @@ export class NameFormComponent implements OnInit, OnDestroy { @Input() set standardized(val: boolean) { if (val != null) { - this.showStd = val; + this.showStd = val; } } @@ -87,11 +102,14 @@ export class NameFormComponent implements OnInit, OnDestroy { set name(name: SubstanceName) { if (name != null) { this.privateName = name; - if (!this.privateName.languages || this.privateName.languages.length === 0) { - this.privateName.languages = ['en']; + if ( + !this.privateName.languages || + this.privateName.languages.length === 0 + ) { + this.privateName.languages = ["en"]; } if (!this.privateName.type) { - this.privateName.type = 'cn'; + this.privateName.type = "cn"; } } } @@ -101,7 +119,7 @@ export class NameFormComponent implements OnInit, OnDestroy { } priorityUpdated(event: MatRadioChange) { - this.privateName.displayName = (event.value === 'true'); + this.privateName.displayName = event.value === "true"; this.priorityUpdate.emit(this.privateName); } @@ -124,9 +142,7 @@ export class NameFormComponent implements OnInit, OnDestroy { deleteName(): void { this.privateName.$$deletedCode = this.utilsService.newUUID(); - if (!this.privateName.name - && !this.privateName.type - ) { + if (!this.privateName.name && !this.privateName.type) { this.deleteTimer = setTimeout(() => { this.nameDeleted.emit(this.privateName); }, 2000); @@ -140,17 +156,26 @@ export class NameFormComponent implements OnInit, OnDestroy { resolve(): void { const dialogRef = this.dialog.open(NameResolverDialogComponent, { - height: 'auto', - width: '800px', - data: {'name': this.privateName.name} + height: "auto", + width: "800px", + data: { name: this.privateName.name }, }); - this.overlayContainer.style.zIndex = '1002'; - dialogRef.afterClosed().subscribe((molfile?: string) => { - this.overlayContainer.style.zIndex = null; - if (molfile != null && molfile !== '') { - this.substanceFormService.resolvedName(molfile); - } - }, () => {}); + this.overlayContainer.style.zIndex = "1002"; + dialogRef.afterClosed().subscribe( + (molfile?: string) => { + this.overlayContainer.style.zIndex = null; + if (molfile != null && molfile !== "") { + this.substanceFormService.resolvedName(molfile); + } + }, + () => {}, + ); + } + + autoResize(event: Event): void { + const textarea = event.target as HTMLTextAreaElement; + textarea.style.height = "auto"; + textarea.style.height = textarea.scrollHeight + "px"; } getNameOrgs(name: SubstanceName): Array { @@ -161,7 +186,7 @@ export class NameFormComponent implements OnInit, OnDestroy { } preventNewLine(event: KeyboardEvent): void { - if (event.key === 'Enter') { + if (event.key === "Enter") { event.preventDefault(); } } diff --git a/src/app/core/substance-form/substance-form-change-reason/substance-form-change-reason.component.html b/src/app/core/substance-form/substance-form-change-reason/substance-form-change-reason.component.html index ebd9cf5d1..6dfc143d7 100644 --- a/src/app/core/substance-form/substance-form-change-reason/substance-form-change-reason.component.html +++ b/src/app/core/substance-form/substance-form-change-reason/substance-form-change-reason.component.html @@ -3,7 +3,7 @@ From 2e0b11a028e8bdbb4923f11c5792fcbfcdda3bdf Mon Sep 17 00:00:00 2001 From: Iaroslav Moskviak Date: Mon, 11 May 2026 16:07:21 -0400 Subject: [PATCH 388/408] amount and redirect fix. --- src/app/core/auth/login/login.component.ts | 4 ++-- .../amount-form/amount-form.component.ts | 10 +++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/app/core/auth/login/login.component.ts b/src/app/core/auth/login/login.component.ts index 60ce66acf..d13b53037 100644 --- a/src/app/core/auth/login/login.component.ts +++ b/src/app/core/auth/login/login.component.ts @@ -67,7 +67,7 @@ export class LoginComponent implements OnInit, OnDestroy { this.loadingService.setLoading(false); if (auth) { const route = this.activatedRoute.snapshot.queryParamMap.get('path') || '/browse-substance'; - this.router.navigate([route]); + this.router.navigateByUrl(route); } else { this.isLoaded = true; this.isLoading = false; @@ -97,7 +97,7 @@ export class LoginComponent implements OnInit, OnDestroy { this.loadingService.setLoading(false); if (auth) { const route = this.activatedRoute.snapshot.queryParamMap.get('path') || '/browse-substance'; - this.router.navigate([route]); + this.router.navigateByUrl(route); } else { this.isLoading = false; } diff --git a/src/app/core/substance-form/amount-form/amount-form.component.ts b/src/app/core/substance-form/amount-form/amount-form.component.ts index ed59a0674..98063b7d8 100644 --- a/src/app/core/substance-form/amount-form/amount-form.component.ts +++ b/src/app/core/substance-form/amount-form/amount-form.component.ts @@ -59,7 +59,7 @@ export class AmountFormComponent implements OnInit { } else { this.averageControl.setValue(''); } - this.privateSubstanceAmount.average = parseInt(value); + this.privateSubstanceAmount.average = parseFloat(value); }); this.lowControl.setValue(this.privateSubstanceAmount.low?.toString() || ''); this.lowControl.valueChanges.subscribe(value => { @@ -72,7 +72,7 @@ export class AmountFormComponent implements OnInit { } else { this.lowControl.setValue(''); } - this.privateSubstanceAmount.low = parseInt(value); + this.privateSubstanceAmount.low = parseFloat(value); }); this.highControl.setValue(this.privateSubstanceAmount.high?.toString() || ''); this.highControl.valueChanges.subscribe(value => { @@ -85,7 +85,7 @@ export class AmountFormComponent implements OnInit { } else { this.highControl.setValue(''); } - this.privateSubstanceAmount.high = parseInt(value); + this.privateSubstanceAmount.high = parseFloat(value); }); this.lowLimitControl.setValue(this.privateSubstanceAmount.lowLimit?.toString() || ''); this.lowLimitControl.valueChanges.subscribe(value => { @@ -97,7 +97,7 @@ export class AmountFormComponent implements OnInit { } else { this.lowLimitControl.setValue(''); } - this.privateSubstanceAmount.lowLimit = parseInt(value); + this.privateSubstanceAmount.lowLimit = parseFloat(value); }); this.highLimitControl.setValue(this.privateSubstanceAmount.highLimit?.toString() || ''); this.highLimitControl.valueChanges.subscribe(value => { @@ -109,7 +109,7 @@ export class AmountFormComponent implements OnInit { } else { this.highLimitControl.setValue(''); } - this.privateSubstanceAmount.highLimit = parseInt(value); + this.privateSubstanceAmount.highLimit = parseFloat(value); }); this.unitsControl.setValue(this.privateSubstanceAmount.units); this.unitsControl.valueChanges.subscribe(value => { From 872f778b697102bc7b46ce42a51b690ffb2de6fb Mon Sep 17 00:00:00 2001 From: Iaroslav Moskviak Date: Thu, 14 May 2026 07:57:16 -0400 Subject: [PATCH 389/408] hasRole fix --- src/app/core/facets-manager/facets-manager.component.ts | 1 - src/app/core/substance-form/can-register-substance-form.ts | 7 +++---- .../core/substance-ssg4m/substance-ssg4m-form.component.ts | 4 ---- 3 files changed, 3 insertions(+), 9 deletions(-) diff --git a/src/app/core/facets-manager/facets-manager.component.ts b/src/app/core/facets-manager/facets-manager.component.ts index 71c4017f8..edcbd9875 100644 --- a/src/app/core/facets-manager/facets-manager.component.ts +++ b/src/app/core/facets-manager/facets-manager.component.ts @@ -308,7 +308,6 @@ export class FacetsManagerComponent implements OnInit, OnDestroy, AfterViewInit if (this.facetsConfig[facetKey].length && (facetKey === 'default' || (facetKey === 'admin' && isAdmin) - || (facetKey !== 'admin' && this.authService.hasRoles(facetKey)) || (facetKey === 'staging' && this.calledFrom === 'staging'))) { this.facetsConfig[facetKey].forEach(facet => { for (let facetIndex = 0; facetIndex < facetsCopy.length; facetIndex++) { diff --git a/src/app/core/substance-form/can-register-substance-form.ts b/src/app/core/substance-form/can-register-substance-form.ts index 2d708f24a..ef52b4d39 100644 --- a/src/app/core/substance-form/can-register-substance-form.ts +++ b/src/app/core/substance-form/can-register-substance-form.ts @@ -24,14 +24,13 @@ export class CanRegisterSubstanceForm implements CanActivate { } else { this.authService.getAuth().subscribe(auth => { if (auth) { - this.authService.hasAnyRolesAsync('DataEntry', 'SuperDataEntry').subscribe(response => { - if (response) { + this.authService.hasSpecificPrivilege('Create').then(canCreate => { + if (canCreate) { observer.next(true); - observer.complete(); } else { observer.next(this.router.parseUrl('/browse-substance')); - observer.complete(); } + observer.complete(); }); } else { const navigationExtras: NavigationExtras = { diff --git a/src/app/core/substance-ssg4m/substance-ssg4m-form.component.ts b/src/app/core/substance-ssg4m/substance-ssg4m-form.component.ts index 1033c0c47..d82f47bb7 100644 --- a/src/app/core/substance-ssg4m/substance-ssg4m-form.component.ts +++ b/src/app/core/substance-ssg4m/substance-ssg4m-form.component.ts @@ -89,8 +89,6 @@ export class SubstanceSsg4ManufactureFormComponent definition: SubstanceFormDefinition; user: string; feature: string; - isAdmin: boolean; - isUpdater: boolean; isAuthenticated: boolean; messageField: string; errorMessage: string; @@ -161,8 +159,6 @@ export class SubstanceSsg4ManufactureFormComponent this.showFormReadOnly = this.activatedRoute.snapshot.queryParams["readonly"] || "false"; this.loadingService.setLoading(true); - this.isAdmin = this.authService.hasRoles("admin"); - this.isUpdater = this.authService.hasAnyRoles("Updater", "SuperUpdater"); this.isAuthenticated = this.authService.getUser() !== ""; this.overlayContainer = this.overlayContainerService.getContainerElement(); this.imported = false; From 0caca41be4114d09824d3cd76b815f50154731fd Mon Sep 17 00:00:00 2001 From: Mitch Miller Date: Mon, 18 May 2026 09:25:54 -0400 Subject: [PATCH 390/408] changing the URL used when creating new users to avoid 401 errors that appear with Spring Boot 3.5.7 --- src/app/core/admin/admin.service.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/core/admin/admin.service.ts b/src/app/core/admin/admin.service.ts index 160b2d443..cf76d41bc 100644 --- a/src/app/core/admin/admin.service.ts +++ b/src/app/core/admin/admin.service.ts @@ -101,7 +101,7 @@ export class AdminService extends BaseHttpService { public addUser(user: UserEditObject): Observable< Auth > { const url = `${(this.configService.configData && this.configService.configData.apiBaseUrl) || '/' }api/v1/`; - return this.http.post< Auth >(`${url}users/`, user); + return this.http.post< Auth >(`${url}users`, user); } public deleteUser(user: string): Observable< Auth > { From add637a8fec0db0743a01f8918d32383aaac0291 Mon Sep 17 00:00:00 2001 From: Jaroslav Iha Date: Wed, 20 May 2026 13:19:56 +0200 Subject: [PATCH 391/408] fix: link to profile/account page --- src/app/core/base/pfda-toolbar/pfda-toolbar.component.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/app/core/base/pfda-toolbar/pfda-toolbar.component.html b/src/app/core/base/pfda-toolbar/pfda-toolbar.component.html index 1a739eeac..a5015c698 100644 --- a/src/app/core/base/pfda-toolbar/pfda-toolbar.component.html +++ b/src/app/core/base/pfda-toolbar/pfda-toolbar.component.html @@ -84,8 +84,8 @@
    - - Profile + + Account -
    +
    @@ -30,34 +31,37 @@
    - + - + - + *ngIf="configSettingsDisplay['limitValue'] || (configSettingsDisplay['limitValue'] === undefined && true)"> + Limit Value +
    - -
    diff --git a/src/app/fda/impurities/impurities-form/impurities-residual-solvents-form/impurities-residual-solvents-form.component.html b/src/app/fda/impurities/impurities-form/impurities-residual-solvents-form/impurities-residual-solvents-form.component.html index 41c7d7402..59f50c81d 100644 --- a/src/app/fda/impurities/impurities-form/impurities-residual-solvents-form/impurities-residual-solvents-form.component.html +++ b/src/app/fda/impurities/impurities-form/impurities-residual-solvents-form/impurities-residual-solvents-form.component.html @@ -16,7 +16,8 @@
    -
    +
    @@ -32,19 +33,21 @@
    - + Pharmaceutical Limit + - + *ngIf="configSettingsDisplay['testType'] || (configSettingsDisplay['testType'] === undefined && true)"> + Test Type + - @@ -52,20 +55,22 @@
    - + Limit Value + - + -
    diff --git a/src/app/fda/impurities/impurities-form/impurities-substance-form/impurities-substance-form.component.html b/src/app/fda/impurities/impurities-form/impurities-substance-form/impurities-substance-form.component.html index 6d3d645e8..610e048d2 100644 --- a/src/app/fda/impurities/impurities-form/impurities-substance-form/impurities-substance-form.component.html +++ b/src/app/fda/impurities/impurities-form/impurities-substance-form/impurities-substance-form.component.html @@ -56,9 +56,9 @@ (configSettingsDisplay['low'] === undefined && true) " > + Assay Low @@ -71,9 +71,9 @@ (configSettingsDisplay['high'] === undefined && true) " > + Assay High @@ -103,9 +103,9 @@ (configSettingsDisplay['comments'] === undefined && true) " > + Comments diff --git a/src/app/fda/impurities/impurities-form/impurities-test-form/impurities-test-form.component.html b/src/app/fda/impurities/impurities-form/impurities-test-form/impurities-test-form.component.html index 93d25cdea..fa389346d 100644 --- a/src/app/fda/impurities/impurities-form/impurities-test-form/impurities-test-form.component.html +++ b/src/app/fda/impurities/impurities-form/impurities-test-form/impurities-test-form.component.html @@ -6,22 +6,13 @@
    -
    -
    @@ -30,408 +21,233 @@
    - + " domain="IMPURITIES_SOURCE_TYPE" title="Source Type" name="sourceType" [model]="impuritiesTest.sourceType" + (valueChange)="impuritiesTest.sourceType = $event"> - + " domain="IMPURITIES_SOURCE" title="Source" name="source" [model]="impuritiesTest.source" + (valueChange)="impuritiesTest.source = $event"> - + "> Source ID
    - + "> Test - + " domain="IMPURITIES_TEST_TYPE" title="Test Type" name="testType" [model]="impuritiesTest.testType" + (valueChange)="impuritiesTest.testType = $event"> - + "> Flow Rate
    - + " domain="IMPURITIES_COLUMN_PACKING_TYPE" title="Column Packing Type" name="columnPackingType" + [model]="impuritiesTest.columnPackingType" (valueChange)="impuritiesTest.columnPackingType = $event"> - - + "> + Column Packing Size + - - + "> + Column Size + - - + "> + Column Temperature +
    - - + "> + Injection Volume Amount + - - + "> + Diluent +
    - + " domain="IMPURITIES_SYSTEM" title="System" name="system" [model]="impuritiesTest.system" + (valueChange)="impuritiesTest.system = $event"> - + " domain="IMPURITIES_MODE" title="Mode" name="mode" [model]="impuritiesTest.mode" + (valueChange)="impuritiesTest.mode = $event"> - + " domain="IMPURITIES_DETECTION_TYPE" title="Detection Type" name="detectionType" + [model]="impuritiesTest.detectionType" (valueChange)="impuritiesTest.detectionType = $event"> - - + "> + Detection Details +
    - - + "> + Suitability Requirements Resolution + - - + "> + Suitability Requirements Relative Standard Deviation +
    - - + "> + Test Description + - - + "> + Comments +
    - - + "> + Sample Solution + - - + "> + System Suitability Solution +
    - - + "> + Standard Solution + - - + "> + Other Solution +
    -
    + ">
    - +
    -
    -
    @@ -439,40 +255,24 @@
    -
    -
    +
    + "> - Solution {{ impuritiesSolution.solutionLetter }} - + Solution {{ impuritiesSolution.solutionLetter }} + -
    @@ -482,20 +282,16 @@ -
    0 - " - > -
    +
    + ">
    Mobile Phase @@ -504,12 +300,8 @@
     
    -
    @@ -524,161 +316,63 @@
    - + - @@ -696,84 +390,53 @@
    - + Impurities            -
    -
    - + "> +


    - + Unspecified Impurities            -
    -
    - + "> +
    -
    +
    \ No newline at end of file diff --git a/src/app/fda/impurities/impurities-form/impurities-unspecified-form/impurities-unspecified-form.component.html b/src/app/fda/impurities/impurities-form/impurities-unspecified-form/impurities-unspecified-form.component.html index d84363f01..75ddfac20 100644 --- a/src/app/fda/impurities/impurities-form/impurities-unspecified-form/impurities-unspecified-form.component.html +++ b/src/app/fda/impurities/impurities-form/impurities-unspecified-form/impurities-unspecified-form.component.html @@ -161,9 +161,9 @@ (configSettingsDisplay['amountValue'] === undefined && true) " > + Amount Value From 58e26fe665108d6b2202d7edb95073b56e0f2dfd Mon Sep 17 00:00:00 2001 From: Iaroslav Moskviak Date: Thu, 21 May 2026 09:20:17 -0400 Subject: [PATCH 393/408] input label overla fix --- .../ingredient-form/ingredient-form.component.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/fda/application/application-form/ingredient-form/ingredient-form.component.scss b/src/app/fda/application/application-form/ingredient-form/ingredient-form.component.scss index cb8b72c0b..e8f49c0a2 100644 --- a/src/app/fda/application/application-form/ingredient-form/ingredient-form.component.scss +++ b/src/app/fda/application/application-form/ingredient-form/ingredient-form.component.scss @@ -261,7 +261,7 @@ legend.border { .related-substance-border .search-form-field .mat-mdc-form-field-infix { - padding-top: 24px; + padding-top: 24px !important; padding-bottom: 8px; } From f977f934b8fc80c03fe0e2e7a5ca6ec321dd2d1e Mon Sep 17 00:00:00 2001 From: Iaroslav Moskviak Date: Thu, 21 May 2026 09:32:09 -0400 Subject: [PATCH 394/408] fix --- .../ingredient-form/ingredient-form.component.scss | 1 + 1 file changed, 1 insertion(+) diff --git a/src/app/fda/application/application-form/ingredient-form/ingredient-form.component.scss b/src/app/fda/application/application-form/ingredient-form/ingredient-form.component.scss index e8f49c0a2..3ed4ad132 100644 --- a/src/app/fda/application/application-form/ingredient-form/ingredient-form.component.scss +++ b/src/app/fda/application/application-form/ingredient-form/ingredient-form.component.scss @@ -258,6 +258,7 @@ legend.border { } ::ng-deep + .related-substance .related-substance-border .search-form-field .mat-mdc-form-field-infix { From d77280c385f7cf9e51fecbea287b9176ec10fc12 Mon Sep 17 00:00:00 2001 From: Iaroslav Moskviak Date: Fri, 22 May 2026 13:59:51 -0400 Subject: [PATCH 395/408] v3000 error fix --- .../structure-import/structure-import.component.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/app/core/structure/structure-import/structure-import.component.ts b/src/app/core/structure/structure-import/structure-import.component.ts index 867711312..6106ca3a6 100644 --- a/src/app/core/structure/structure-import/structure-import.component.ts +++ b/src/app/core/structure/structure-import/structure-import.component.ts @@ -4,11 +4,11 @@ import { FormControl } from '@angular/forms'; import { GoogleAnalyticsService } from '../../google-analytics/google-analytics.service'; import { StructureService } from '../structure.service'; -@Component({ - selector: 'app-structure-import', - templateUrl: './structure-import.component.html', - styleUrls: ['./structure-import.component.scss'], - standalone: false +@Component({ + selector: 'app-structure-import', + templateUrl: './structure-import.component.html', + styleUrls: ['./structure-import.component.scss'], + standalone: false }) export class StructureImportComponent implements OnInit { isLoading = false; @@ -32,7 +32,7 @@ export class StructureImportComponent implements OnInit { this.isLoading = true; this.structureService.interpretStructure(this.importTextControl.value).subscribe(response => { this.isLoading = false; - if (response && response.structure && response.structure.molfile) { + if (response && response.structure && response.structure.molfile && response.structure.smiles) { this.gaService.sendEvent('structureImport', 'button:import', 'file imported'); this.dialogRef.close(response); } else { From e8483ec3dc24b9e70b348705c66c77a63cf3498f Mon Sep 17 00:00:00 2001 From: Iaroslav Moskviak Date: Fri, 22 May 2026 14:14:04 -0400 Subject: [PATCH 396/408] error message fix --- .../structure/structure-import/structure-import.component.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/core/structure/structure-import/structure-import.component.ts b/src/app/core/structure/structure-import/structure-import.component.ts index 6106ca3a6..95c85b130 100644 --- a/src/app/core/structure/structure-import/structure-import.component.ts +++ b/src/app/core/structure/structure-import/structure-import.component.ts @@ -37,7 +37,7 @@ export class StructureImportComponent implements OnInit { this.dialogRef.close(response); } else { this.messageClass = 'error'; - this.message = 'You need to enter a valid molfile or smiles'; + this.message = 'Please enter a valid v2000 molfile or smiles'; this.gaService.sendException('wrong structure data imported'); } From d4df8e39ab906e24d83e15793ac10a554bbc247a Mon Sep 17 00:00:00 2001 From: Iaroslav Moskviak Date: Fri, 22 May 2026 14:54:43 -0400 Subject: [PATCH 397/408] scroll on hover fix --- src/styles/_misc.scss | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/src/styles/_misc.scss b/src/styles/_misc.scss index e2b3a9f2b..47f087c7d 100644 --- a/src/styles/_misc.scss +++ b/src/styles/_misc.scss @@ -86,7 +86,22 @@ body { } .responsive { - overflow: auto; + overflow: auto; + scrollbar-width: thin; // Firefox: thin fixed-width scrollbar + + &::-webkit-scrollbar { + width: 8px; + height: 8px; + } + + &::-webkit-scrollbar-thumb { + background: rgba(0, 0, 0, 0.25); + border-radius: 4px; + } + + &::-webkit-scrollbar-track { + background: transparent; + } } .text-center { From 16ab3181d79a8f1233f7e6d42cf33de268265ffb Mon Sep 17 00:00:00 2001 From: Iaroslav Moskviak Date: Tue, 26 May 2026 11:26:39 -0400 Subject: [PATCH 398/408] approve button fix --- src/app/core/substance-form/substance-form.component.ts | 4 ++-- src/app/core/substance-ssg2/substance-ssg2-form.component.ts | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/app/core/substance-form/substance-form.component.ts b/src/app/core/substance-form/substance-form.component.ts index d4a54a0d9..6533ba467 100644 --- a/src/app/core/substance-form/substance-form.component.ts +++ b/src/app/core/substance-form/substance-form.component.ts @@ -29,7 +29,7 @@ import {JsonDialogComponent} from '@gsrs-core/substance-form/json-dialog/json-di import * as _ from 'lodash'; import {Title} from '@angular/platform-browser'; import {AuthService} from '@gsrs-core/auth'; -import {take, map} from 'rxjs/operators'; +import {take, map, filter} from 'rxjs/operators'; import {MatExpansionPanel} from '@angular/material/expansion'; import {SubmitSuccessDialogComponent} from './submit-success-dialog/submit-success-dialog.component'; import { @@ -457,7 +457,7 @@ export class SubstanceFormComponent implements OnInit, AfterViewInit, AfterViewC }); }); this.subscriptions.push(definitionSubscription); - this.authService.getAuth().pipe(take(1)).subscribe(auth => { + this.authService.getAuth().pipe(filter(auth => auth != null), take(1)).subscribe(auth => { this.user = auth.identifier; setTimeout(() => { this.canApprove = this.canBeApproved(); diff --git a/src/app/core/substance-ssg2/substance-ssg2-form.component.ts b/src/app/core/substance-ssg2/substance-ssg2-form.component.ts index ddb7da86c..5f068b15b 100644 --- a/src/app/core/substance-ssg2/substance-ssg2-form.component.ts +++ b/src/app/core/substance-ssg2/substance-ssg2-form.component.ts @@ -25,7 +25,7 @@ import { JsonDialogComponent } from '@gsrs-core/substance-form/json-dialog/json- import * as _ from 'lodash'; import { Title } from '@angular/platform-browser'; import { AuthService } from '@gsrs-core/auth'; -import { take, map } from 'rxjs/operators'; +import { take, map, filter } from 'rxjs/operators'; import { MatExpansionPanel } from '@angular/material/expansion'; import { SubmitSuccessDialogComponent } from '../substance-form/submit-success-dialog/submit-success-dialog.component'; import { MergeConceptDialogComponent } from '@gsrs-core/substance-form/merge-concept-dialog/merge-concept-dialog.component'; @@ -351,7 +351,7 @@ export class SubstanceSsg2FormComponent implements OnInit, AfterViewInit, OnDest }); }); this.subscriptions.push(definitionSubscription); - this.authService.getAuth().pipe(take(1)).subscribe(auth => { + this.authService.getAuth().pipe(filter(auth => auth != null), take(1)).subscribe(auth => { this.user = auth.identifier; setTimeout(() => { this.canApprove = this.canBeApproved(); From 0195d896556742b3685e1eea0ba117f0dc2d2f75 Mon Sep 17 00:00:00 2001 From: Amit Viraktamath Date: Fri, 12 Jun 2026 14:21:46 -0400 Subject: [PATCH 399/408] chore: add SECURITY.md NCATS GH org-wide security policy rollout --- SECURITY.md | 94 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 94 insertions(+) create mode 100644 SECURITY.md diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 000000000..1ef015379 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,94 @@ +# Security Policy + +This policy governs vulnerability research and reporting for NIH systems and services under the [HHS Vulnerability Disclosure Policy](https://www.hhs.gov/vulnerability-disclosure-policy/index.html). + +--- + +## Reporting a Vulnerability + +**Report all security issues using GitHub's private vulnerability reporting feature.** + +Click **"Report a vulnerability"** on the [Security Advisories](../../security/advisories) tab. Do not open a public issue or report through email, Slack, or any other channel. + +Your report will be received securely, kept confidential, and routed to the right people. See [GitHub's documentation](https://docs.github.com/en/code-security/security-advisories/guidance-on-reporting-and-writing-information-about-vulnerabilities/privately-reporting-a-security-vulnerability) for how the process works. + +> **Note:** The HHS VDP also accepts reports at [hhs.responsibledisclosure.com](https://hhs.responsibledisclosure.com). For vulnerabilities in this repository or NIH systems hosted here, use the GitHub report link above so the responsible team is notified directly. + +Please include: +- What the vulnerability is and its potential impact +- Where it was found +- Steps to reproduce (proof-of-concept scripts or screenshots are helpful) + +--- + +## Authorization + +*Issued under the [HHS VDP — Authorization](https://www.hhs.gov/vulnerability-disclosure-policy/index.html#authorization).* + +Good faith research conducted under this policy is authorized. NIH and HHS will work with you to resolve issues quickly and will not pursue legal action related to your research. + +--- + +## Guidelines + +*Issued under the [HHS VDP — Guidelines](https://www.hhs.gov/vulnerability-disclosure-policy/index.html#guidelines).* + +Authorized research means you: + +- Notify us as soon as possible after discovering a real or potential security issue +- Avoid privacy violations, user experience degradation, production system disruption, and data destruction or manipulation +- Use exploits only to the extent necessary to confirm a vulnerability — do not exfiltrate data, establish access or persistence, or pivot to other systems +- Allow a reasonable amount of time to resolve the issue before public disclosure +- Do not compromise the privacy, safety, intellectual property, or financial interests of HHS/NIH personnel or third parties + +If you discover a vulnerability or encounter any sensitive data (PII, financial, proprietary, or trade secrets), **stop testing, report immediately via the GitHub private vulnerability report, and do not disclose the data to anyone else**. + +--- + +## Scope + +*Issued under the [HHS VDP — Scope](https://www.hhs.gov/vulnerability-disclosure-policy/index.html#scope).* + +`nih.gov` and all its subdomains are in scope. Vendor systems are out of scope — report those to the vendor directly. + +Unsure if a system is in scope? Contact [support@responsibledisclosure.com](mailto:support@responsibledisclosure.com) before testing. + +--- + +## Rules of Engagement + +*Issued under the [HHS VDP — Rules of Engagement](https://www.hhs.gov/vulnerability-disclosure-policy/index.html#rules-engagement).* + +**Must not:** +- Test systems outside the scope above +- Disclose vulnerability information except as described in this policy +- Conduct physical testing, social engineering, or phishing +- Execute denial-of-service or resource exhaustion attacks +- Introduce malicious software +- Degrade, impair, disrupt, or disable HHS/NIH systems +- Test third-party applications or services that integrate with NIH systems +- Delete, alter, share, retain, destroy, or exfiltrate HHS/NIH data +- Use an exploit to establish access, persistence, or pivot to other systems + +**May:** +- View or store NIH nonpublic data only as necessary to document a potential vulnerability + +**Must:** +- Cease testing and immediately report any vulnerability or nonpublic data exposure via the GitHub private vulnerability report +- Purge any stored NIH nonpublic data upon reporting + +--- + +## Disclosure + +*Issued under the [HHS VDP — Disclosure](https://www.hhs.gov/vulnerability-disclosure-policy/index.html#disclosure).* + +Do not share details about discovered vulnerabilities for **90 calendar days** after receiving our acknowledgment. If you believe earlier disclosure is warranted, coordinate with us in advance. + +Reports may be shared with [CISA](https://www.cisa.gov/) and affected vendors under their [coordinated vulnerability disclosure process](https://www.cisa.gov/coordinated-vulnerability-disclosure-process). We will not share your name or contact information without explicit permission. + +--- + +## Questions + +Email [HHS.Cybersecurity@hhs.gov](mailto:HHS.Cybersecurity@hhs.gov) or see the [HHS VDP](https://www.hhs.gov/vulnerability-disclosure-policy/index.html#questions). From 40153cee092effd7b7c9530590fe9762e6b7722a Mon Sep 17 00:00:00 2001 From: Mitch Miller Date: Mon, 15 Jun 2026 20:09:07 -0400 Subject: [PATCH 400/408] display correct version of a polymer structure --- .../substance-polymer-structure.component.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/core/substance-details/substance-polymer-structure/substance-polymer-structure.component.html b/src/app/core/substance-details/substance-polymer-structure/substance-polymer-structure.component.html index 7b6062794..f45ca3618 100644 --- a/src/app/core/substance-details/substance-polymer-structure/substance-polymer-structure.component.html +++ b/src/app/core/substance-details/substance-polymer-structure/substance-polymer-structure.component.html @@ -7,7 +7,7 @@
    - +
    From 0cfc896c5c6e1a9599aa92fe0d63b63ec4f6679a Mon Sep 17 00:00:00 2001 From: alx652 Date: Tue, 16 Jun 2026 11:52:18 -0400 Subject: [PATCH 401/408] lock in version appropriate for angular 20 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 7ac73b101..890a6d2d3 100644 --- a/package.json +++ b/package.json @@ -65,7 +65,7 @@ "ng-multiselect-dropdown": "^1.0.0", "ngx-json-viewer": "^3.2.1", "ngx-moment": "^6.0.2", - "ngx-schema-form": "^2.14.1", + "ngx-schema-form": "2.14.1", "pako": "^2.1.0", "primeng": "^20.3.0", "reflect-metadata": "0.1.13", From dd08baafca6e8df680f7d5059334cb4c20d9ecb7 Mon Sep 17 00:00:00 2001 From: Jaroslav Iha Date: Wed, 24 Jun 2026 11:32:44 +0200 Subject: [PATCH 402/408] fix save local batch; add message to failed load backup --- .../substance-drafts.component.html | 7 +- .../substance-drafts.component.scss | 19 ++- .../substance-drafts.component.ts | 25 +++- .../substance-ssg4m-form.component.ts | 137 ++++++++++++++++-- 4 files changed, 167 insertions(+), 21 deletions(-) diff --git a/src/app/core/substance-form/substance-drafts/substance-drafts.component.html b/src/app/core/substance-form/substance-drafts/substance-drafts.component.html index d563ae831..5c7cc883a 100644 --- a/src/app/core/substance-form/substance-drafts/substance-drafts.component.html +++ b/src/app/core/substance-form/substance-drafts/substance-drafts.component.html @@ -149,8 +149,11 @@ > Load A Backup -
    - {{ filename ? filename : "no file chosen" }} +
    + {{ loadError ? loadError : (filename ? filename : "no file chosen") }}
    button { + flex-shrink: 0; +} + +.file-name { + flex: 1 1 auto; + min-width: 0; + word-break: break-word; + align-self: center; +} + +.file-name.error { + color: var(--error-dialog-color); + font-weight: 500; } ::ng-deep .mat-mdc-dialog-container .mat-mdc-dialog-content { diff --git a/src/app/core/substance-form/substance-drafts/substance-drafts.component.ts b/src/app/core/substance-form/substance-drafts/substance-drafts.component.ts index a82e1d08c..89cf47378 100644 --- a/src/app/core/substance-form/substance-drafts/substance-drafts.component.ts +++ b/src/app/core/substance-form/substance-drafts/substance-drafts.component.ts @@ -49,6 +49,7 @@ export class SubstanceDraftsComponent implements OnInit { formState: FormState = FormState.DRAFT_LIST; isLoading: boolean = false; validatedDrafts: Array = []; + loadError: string | null = null; constructor( private substanceFormService: SubstanceFormService, @@ -90,8 +91,27 @@ export class SubstanceDraftsComponent implements OnInit { var reader = new FileReader(); reader.onload = (e) => { const file = e.target.result; - this.filtered = JSON.parse(file); - this.values = JSON.parse(file); + try { + const parsed = JSON.parse(file); + if (!Array.isArray(parsed)) { + throw new Error('Backup file must contain an array of drafts.'); + } + const isValidDraft = (entry: any) => + entry && typeof entry === 'object' && entry.substance && entry.date; + if (parsed.length > 0 && !parsed.every(isValidDraft)) { + throw new Error('Backup file does not contain valid draft entries.'); + } + this.loadError = null; + this.filtered = parsed; + this.values = parsed; + } catch (err) { + this.loadError = `${this.filename}: Unable to load the file. Please choose a valid GSRS drafts backup (.json).`; + this.file = null; + } + }; + reader.onerror = () => { + this.loadError = `${this.filename}: Unable to read the file. Please try again.`; + this.file = null; }; reader.readAsText(this.file); } @@ -354,6 +374,7 @@ export class SubstanceDraftsComponent implements OnInit { if (event.target.files.length > 0) { this.file = event.target.files[0]; this.filename = this.file.name; + this.loadError = null; // this.uploadForm.get('file').setValue(this.file); this.readFile() } diff --git a/src/app/core/substance-ssg4m/substance-ssg4m-form.component.ts b/src/app/core/substance-ssg4m/substance-ssg4m-form.component.ts index d82f47bb7..22de5637d 100644 --- a/src/app/core/substance-ssg4m/substance-ssg4m-form.component.ts +++ b/src/app/core/substance-ssg4m/substance-ssg4m-form.component.ts @@ -18,10 +18,11 @@ import { import { OverlayContainer } from "@angular/cdk/overlay"; import { MatExpansionPanel } from "@angular/material/expansion"; import { MatDialog } from "@angular/material/dialog"; -import { take, map } from "rxjs/operators"; -import { Subscription, Observable } from "rxjs"; +import { take, map, catchError } from "rxjs/operators"; +import { Subscription, Observable, forkJoin, of } from "rxjs"; import * as _ from "lodash"; import * as moment from "moment"; +import JSZip from "jszip"; import { Title } from "@angular/platform-browser"; import { DomSanitizer, SafeUrl } from "@angular/platform-browser"; // GSRS Import @@ -681,9 +682,6 @@ export class SubstanceSsg4ManufactureFormComponent const timestamp = moment(new Date()).format("MMM-DD-YYYY_H-mm-ss"); - // Download the SSG4M substance file - this.downloadFile(JSON.stringify(json), "SSG4m_" + timestamp + ".json"); - // Collect all refuuids from materials in the SSG4M hierarchy const referencedUuids = new Set(); if (json.specifiedSubstanceG4m && json.specifiedSubstanceG4m.process) { @@ -711,29 +709,127 @@ export class SubstanceSsg4ManufactureFormComponent } } - // Find matching drafts in localStorage and download each as a separate file + // Find matching drafts in localStorage + const draftSubstances: any[] = []; + const foundUuids = new Set(); const keys = Object.keys(localStorage); for (const key of keys) { - if (key.startsWith("gsrs-draft-")) { + if (!key.startsWith("gsrs-draft-")) { + continue; + } + try { const draft = JSON.parse(localStorage.getItem(key)); if ( draft && draft.substance && - referencedUuids.has(draft.substance.uuid) + draft.substance.uuid && + referencedUuids.has(draft.substance.uuid) && + !foundUuids.has(draft.substance.uuid) ) { - const name = draft.substance.uuid || draft.name || "unknown"; - const safeName = name.replace(/[^a-zA-Z0-9_-]/g, "_"); - this.downloadFile( - JSON.stringify(draft.substance), - "Draft_" + safeName + "_" + timestamp + ".json", - ); + this.removeTmpStructureIdFields(draft.substance); + draftSubstances.push(draft.substance); + foundUuids.add(draft.substance.uuid); } + } catch (e) { + // skip unparsable draft entry } } + + // Fetch remaining referenced substances from the DB + const dbUuids = Array.from(referencedUuids).filter( + (uuid) => !foundUuids.has(uuid), + ); + const dbFetches: Observable[] = dbUuids.map( + (uuid) => + this.substanceService.getSubstanceDetails(uuid).pipe( + take(1), + catchError(() => of(null)), + ), + ); + const fetch$: Observable<(SubstanceDetail | null)[]> = dbFetches.length + ? forkJoin(dbFetches) + : of([]); + + this.loadingService.setLoading(true); + fetch$.pipe(take(1)).subscribe( + (dbResults) => { + const dbSubstances = (dbResults || []).filter( + (s): s is SubstanceDetail => !!s, + ); + for (const s of dbSubstances) { + this.removeTmpStructureIdFields(s); + } + + // Build a multi-file zip: one JSON per substance + const zip = new JSZip(); + const usedFileNames = new Set(); + + const mainUuidPart = json && json.uuid + ? String(json.uuid).replace(/[^a-zA-Z0-9_-]/g, "_") + "_" + : ""; + const mainName = this.uniqueFileName( + "SSG4m_" + mainUuidPart + timestamp, + usedFileNames, + ); + zip.file(mainName, JSON.stringify(json, null, 2)); + + for (const draftSub of draftSubstances) { + const fileName = this.uniqueFileName( + "Draft_" + (this.getSubstanceFileLabel(draftSub) || "unknown"), + usedFileNames, + ); + zip.file(fileName, JSON.stringify(draftSub, null, 2)); + } + + for (const dbSub of dbSubstances) { + const fileName = this.uniqueFileName( + (this.getSubstanceFileLabel(dbSub) || "unknown"), + usedFileNames, + ); + zip.file(fileName, JSON.stringify(dbSub, null, 2)); + } + + zip + .generateAsync({ + type: "blob", + compression: "DEFLATE", + compressionOptions: { level: 6 }, + }) + .then( + (blob) => { + this.downloadBlob(blob, "SSG4m_batch_" + timestamp + ".zip"); + this.loadingService.setLoading(false); + }, + () => { + this.loadingService.setLoading(false); + }, + ); + }, + () => { + this.loadingService.setLoading(false); + }, + ); } - private downloadFile(content: string, fileName: string): void { - const blob = new Blob([content], { type: "application/json" }); + private getSubstanceFileLabel(sub: any): string { + const raw = + (sub && (sub.uuid || (sub.names && sub.names[0] && sub.names[0].name))) || + ""; + return String(raw).replace(/[^a-zA-Z0-9_-]/g, "_"); + } + + private uniqueFileName(baseName: string, used: Set): string { + let candidate = baseName + ".json"; + let counter = 2; + while (used.has(candidate)) { + candidate = baseName + "_" + counter + ".json"; + counter++; + } + used.add(candidate); + return candidate; + } + + private downloadBlob(blob: Blob, fileName: string): void { const url = URL.createObjectURL(blob); const a = document.createElement("a"); a.href = url; @@ -744,6 +840,15 @@ export class SubstanceSsg4ManufactureFormComponent URL.revokeObjectURL(url); } + private downloadFile( + content: BlobPart, + fileName: string, + mimeType: string = "application/json", + ): void { + const blob = new Blob([content], { type: mimeType }); + this.downloadBlob(blob, fileName); + } + checkSsg4mServerStatus(): void { // Check Microservice Server Status this.substanceSsg4mService From d4aaef6f528070923b90fa8c581abee06c1e3e56 Mon Sep 17 00:00:00 2001 From: Jaroslav Iha Date: Wed, 24 Jun 2026 11:52:08 +0200 Subject: [PATCH 403/408] add jszip package --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index 890a6d2d3..51b215b30 100644 --- a/package.json +++ b/package.json @@ -59,6 +59,7 @@ "jsonpath": "^1.1.1", "jspdf": "^2.5.1", "jspdf-autotable": "^3.8.2", + "jszip": "^3.10.1", "lodash": "^4.18.1", "lucene-query-parser": "1.2.0", "moment": "2.29.4", From 2c97ba6ec2340e644ca96ef28d1316f0bf5b4a12 Mon Sep 17 00:00:00 2001 From: Jaroslav Iha Date: Tue, 28 Jul 2026 11:44:01 +0200 Subject: [PATCH 404/408] frontend build fix --- src/app/core/substance-form/substance-form.component.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/app/core/substance-form/substance-form.component.ts b/src/app/core/substance-form/substance-form.component.ts index 1e88559bd..96d688701 100644 --- a/src/app/core/substance-form/substance-form.component.ts +++ b/src/app/core/substance-form/substance-form.component.ts @@ -101,7 +101,6 @@ export class SubstanceFormComponent implements OnInit, AfterViewInit, AfterViewC canUpdate: boolean; canMakeAdvancedEdits: boolean; messageField: string; - isPfdaVersion: boolean = false; uuid: string; substanceClass: string; drafts: Array; @@ -320,7 +319,7 @@ export class SubstanceFormComponent implements OnInit, AfterViewInit, AfterViewC if (this.configService.configData && this.configService.configData.useApprovalAPI) { this.useApprovalAPI = this.configService.configData.useApprovalAPI; } - this.isPfdaVersion = this.configService.configData.isPfdaVersion; + this.isPfdaVersion = this.configService.configData?.isPfdaVersion ?? false; this.canUpdate = await this.authService.hasSpecificPrivilege("Edit"); this.canMakeAdvancedEdits = await this.authService.hasSpecificPrivilege("Edit Public Data"); this.userCanApprove = await this.authService.hasSpecificPrivilege("Approve Records"); From c73fb4c9aff6f736fc12a7189c348fe03108a6af Mon Sep 17 00:00:00 2001 From: Jaroslav Iha Date: Tue, 28 Jul 2026 14:35:22 +0200 Subject: [PATCH 405/408] fix gsrs dropdown z index --- src/app/core/base/pfda-toolbar/pfda-toolbar.component.scss | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/app/core/base/pfda-toolbar/pfda-toolbar.component.scss b/src/app/core/base/pfda-toolbar/pfda-toolbar.component.scss index 749b3e67d..3b69b8903 100644 --- a/src/app/core/base/pfda-toolbar/pfda-toolbar.component.scss +++ b/src/app/core/base/pfda-toolbar/pfda-toolbar.component.scss @@ -18,7 +18,10 @@ $screenMedium: 1045px; line-height: 16px; padding: 0 8px; height: auto; - z-index: 1002; + // !important is required to beat the global `.mat-toolbar:not(.mat-toolbar-multiple-rows)` + // override (z-index: 1001), which otherwise ties with the substance form `.top-fixed` header + // and pushes the GSRS dropdown behind it. + z-index: 1002 !important; a { color: inherit; From 9789a418f0f57a997f2f358d57ba907e2e3de995 Mon Sep 17 00:00:00 2001 From: Jaroslav Iha Date: Wed, 29 Jul 2026 12:38:05 +0200 Subject: [PATCH 406/408] gsrs dropdown fix test 2 --- .../base/pfda-toolbar/pfda-toolbar.component.scss | 7 ++++--- src/styles/_material-overrides.scss | 13 +++++++++++++ 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/src/app/core/base/pfda-toolbar/pfda-toolbar.component.scss b/src/app/core/base/pfda-toolbar/pfda-toolbar.component.scss index 3b69b8903..25d5dcbf5 100644 --- a/src/app/core/base/pfda-toolbar/pfda-toolbar.component.scss +++ b/src/app/core/base/pfda-toolbar/pfda-toolbar.component.scss @@ -18,9 +18,10 @@ $screenMedium: 1045px; line-height: 16px; padding: 0 8px; height: auto; - // !important is required to beat the global `.mat-toolbar:not(.mat-toolbar-multiple-rows)` - // override (z-index: 1001), which otherwise ties with the substance form `.top-fixed` header - // and pushes the GSRS dropdown behind it. + // Must beat the global `.mat-toolbar:not(.mat-toolbar-multiple-rows)` rule (z-index: 1001), + // which otherwise ties with the substance form `.top-fixed` header and pushes the + // `.gsrs-dropdown` behind it. See also the `.mat-toolbar.pfda-toolbar` rule in + // styles/_material-overrides.scss, which enforces the same value by specificity. z-index: 1002 !important; a { diff --git a/src/styles/_material-overrides.scss b/src/styles/_material-overrides.scss index fd1da3eaf..8a192ba6e 100644 --- a/src/styles/_material-overrides.scss +++ b/src/styles/_material-overrides.scss @@ -1064,6 +1064,19 @@ app-loading .mat-mdc-progress-spinner { } } +// The pFDA toolbar hosts the CSS-hover `.gsrs-dropdown`, which is painted inside the +// toolbar's own stacking context. The toolbar must therefore outrank fixed page headers +// such as the substance form `.top-fixed` (z-index: 1001), otherwise the dropdown is +// hidden behind them. The generic single-row rule above has the same specificity as the +// component's `.pfda-toolbar` style, so which one wins depends on stylesheet order: in +// `ng serve` the component style is injected last and wins, but in production +// `inlineCritical` relocates styles.css so the generic rule wins and drops the toolbar +// back to 1001. Winning on specificity here makes the outcome build-independent. +.mat-toolbar.pfda-toolbar, +.mat-mdc-toolbar.pfda-toolbar { + z-index: 1002; +} + // Fix toolbar row heights to match Angular 14 .mat-toolbar-row, .mat-mdc-toolbar-row { From bc82fdb43a97d4c1d22b52d0f751ba6025d5c845 Mon Sep 17 00:00:00 2001 From: Jaroslav Iha Date: Wed, 29 Jul 2026 15:02:17 +0200 Subject: [PATCH 407/408] gsrs dropdown fix test 3 --- .../pfda-toolbar/pfda-toolbar.component.scss | 5 +++-- src/styles/_material-overrides.scss | 21 +++++++++++-------- 2 files changed, 15 insertions(+), 11 deletions(-) diff --git a/src/app/core/base/pfda-toolbar/pfda-toolbar.component.scss b/src/app/core/base/pfda-toolbar/pfda-toolbar.component.scss index 25d5dcbf5..42665ef5b 100644 --- a/src/app/core/base/pfda-toolbar/pfda-toolbar.component.scss +++ b/src/app/core/base/pfda-toolbar/pfda-toolbar.component.scss @@ -20,8 +20,9 @@ $screenMedium: 1045px; height: auto; // Must beat the global `.mat-toolbar:not(.mat-toolbar-multiple-rows)` rule (z-index: 1001), // which otherwise ties with the substance form `.top-fixed` header and pushes the - // `.gsrs-dropdown` behind it. See also the `.mat-toolbar.pfda-toolbar` rule in - // styles/_material-overrides.scss, which enforces the same value by specificity. + // `.gsrs-dropdown` behind it. `!important` is required because production builds move + // styles.css after the component styles Angular injects into . See the matching + // `.mat-toolbar.pfda-toolbar` rule in styles/_material-overrides.scss. z-index: 1002 !important; a { diff --git a/src/styles/_material-overrides.scss b/src/styles/_material-overrides.scss index 8a192ba6e..f808323a5 100644 --- a/src/styles/_material-overrides.scss +++ b/src/styles/_material-overrides.scss @@ -1064,17 +1064,20 @@ app-loading .mat-mdc-progress-spinner { } } -// The pFDA toolbar hosts the CSS-hover `.gsrs-dropdown`, which is painted inside the -// toolbar's own stacking context. The toolbar must therefore outrank fixed page headers -// such as the substance form `.top-fixed` (z-index: 1001), otherwise the dropdown is -// hidden behind them. The generic single-row rule above has the same specificity as the -// component's `.pfda-toolbar` style, so which one wins depends on stylesheet order: in -// `ng serve` the component style is injected last and wins, but in production -// `inlineCritical` relocates styles.css so the generic rule wins and drops the toolbar -// back to 1001. Winning on specificity here makes the outcome build-independent. +// The pFDA toolbar hosts the CSS-hover `.gsrs-dropdown`, which paints inside the toolbar's +// own stacking context. The toolbar must therefore outrank fixed page headers such as the +// substance form `.top-fixed` (z-index: 1001), otherwise the dropdown is hidden behind them. +// +// The generic single-row rule above also matches `.pfda-toolbar` and would reset it to 1001. +// Note both selectors have equal specificity (0,2,0) -- `:not()` contributes the specificity +// of its argument -- so a plain declaration would be decided by stylesheet order. That order +// is not stable across builds: `ng serve` injects component styles last (component wins), +// while production `inlineCritical`/beasties moves styles.css to the end of , placing it +// after the component styles Angular injects into (styles.css wins). `!important` +// removes the ambiguity so the toolbar keeps z-index 1002 in every build. .mat-toolbar.pfda-toolbar, .mat-mdc-toolbar.pfda-toolbar { - z-index: 1002; + z-index: 1002 !important; } // Fix toolbar row heights to match Angular 14 From ffd8b5df4107e4d21f9a4476a4f3901624ffd7e8 Mon Sep 17 00:00:00 2001 From: Jaroslav Iha Date: Wed, 26 Aug 2026 11:06:54 +0200 Subject: [PATCH 408/408] update: renew JSDraw2 expired license --- src/app/core/assets/jsdraw/Scilligence.JSDraw2.Pro.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/app/core/assets/jsdraw/Scilligence.JSDraw2.Pro.js b/src/app/core/assets/jsdraw/Scilligence.JSDraw2.Pro.js index 252281548..a38637d6f 100644 --- a/src/app/core/assets/jsdraw/Scilligence.JSDraw2.Pro.js +++ b/src/app/core/assets/jsdraw/Scilligence.JSDraw2.Pro.js @@ -25,8 +25,8 @@ JSDraw2.password = { encrypt: true, key: null, iv: null }; // Place the license code below // Licensed to: FDA // Product: JSDraw -// Expiration Date: 2026-Jul-30 -JSDraw2.licensecode='405562537916781761723242424242424131213141512181'; +// Expiration Date: 2027-Jul-30 +JSDraw2.licensecode='405562536916781761723242424242424131213141512181';
    {{ columnName }} +
    - - - + +
    - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + +