diff --git a/angular.json b/angular.json index a799f5f67..97bef37ee 100644 --- a/angular.json +++ b/angular.json @@ -48,7 +48,6 @@ "styles": [ "src/styles/styles.scss", "node_modules/primeflex/primeflex.css", - "node_modules/@fortawesome/fontawesome-free/css/all.min.css", "node_modules/ngx-markdown-editor/assets/highlight.js/agate.min.css" ], "stylePreprocessorOptions": { @@ -74,7 +73,8 @@ "maximumError": "25kB" } ], - "outputHashing": "all" + "outputHashing": "all", + "optimization": true }, "analyze-bundle": { "sourceMap": true, @@ -147,15 +147,15 @@ }, "development": { "buildTarget": "osf:build:development", - "hmr": false + "hmr": true }, "docker": { "buildTarget": "osf:build:docker", - "hmr": false + "hmr": true }, "staging": { "buildTarget": "osf:build:staging", - "hmr": false + "hmr": true }, "test": { "buildTarget": "osf:build:test", diff --git a/jest.config.js b/jest.config.js index ecdfa34e0..73c577307 100644 --- a/jest.config.js +++ b/jest.config.js @@ -50,10 +50,10 @@ module.exports = { extensionsToTreatAsEsm: ['.ts'], coverageThreshold: { global: { - branches: 24.1, - functions: 28.73, - lines: 56.52, - statements: 56.82, + branches: 28.0, + functions: 32.0, + lines: 60.28, + statements: 60.77, }, }, watchPathIgnorePatterns: [ diff --git a/package-lock.json b/package-lock.json index 03cc9e29a..71fd20064 100644 --- a/package-lock.json +++ b/package-lock.json @@ -23,8 +23,6 @@ "@newrelic/browser-agent": "^1.301.0", "@ngx-translate/core": "^16.0.4", "@ngx-translate/http-loader": "^16.0.1", - "@ngxs/devtools-plugin": "^19.0.0", - "@ngxs/logger-plugin": "^19.0.0", "@ngxs/store": "^19.0.0", "@primeng/themes": "^19.0.9", "@sentry/angular": "^10.10.0", @@ -6470,42 +6468,6 @@ "@angular/core": ">=16" } }, - "node_modules/@ngxs/devtools-plugin": { - "version": "19.0.0", - "resolved": "https://registry.npmjs.org/@ngxs/devtools-plugin/-/devtools-plugin-19.0.0.tgz", - "integrity": "sha512-z3O/G0fGeSc/mQRMBWwQ98W+kB0QpIMPZg2FLIubyZwWydouVatjhYck4IDLR/h5i6lq4McKioMK2tn/mXZqnQ==", - "license": "MIT", - "dependencies": { - "tslib": "^2.3.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/ngxs" - }, - "peerDependencies": { - "@angular/core": ">=19.0.0 <20.0.0", - "@ngxs/store": "^19.0.0 || ^19.0.0-dev", - "rxjs": ">=6.5.5" - } - }, - "node_modules/@ngxs/logger-plugin": { - "version": "19.0.0", - "resolved": "https://registry.npmjs.org/@ngxs/logger-plugin/-/logger-plugin-19.0.0.tgz", - "integrity": "sha512-qLGB4muiLlDDYVUOLgXalWYjd3DumMJDM/JCuyQD7xJl9wwixbMWVME1tnZ1e2/FFqKRPuL+54OnEtQ3SrSpOg==", - "license": "MIT", - "dependencies": { - "tslib": "^2.3.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/ngxs" - }, - "peerDependencies": { - "@angular/core": ">=19.0.0 <20.0.0", - "@ngxs/store": "^19.0.0 || ^19.0.0-dev", - "rxjs": ">=6.5.5" - } - }, "node_modules/@ngxs/store": { "version": "19.0.0", "resolved": "https://registry.npmjs.org/@ngxs/store/-/store-19.0.0.tgz", diff --git a/package.json b/package.json index dc2585412..fff7a3540 100644 --- a/package.json +++ b/package.json @@ -47,8 +47,6 @@ "@newrelic/browser-agent": "^1.301.0", "@ngx-translate/core": "^16.0.4", "@ngx-translate/http-loader": "^16.0.1", - "@ngxs/devtools-plugin": "^19.0.0", - "@ngxs/logger-plugin": "^19.0.0", "@ngxs/store": "^19.0.0", "@primeng/themes": "^19.0.9", "@sentry/angular": "^10.10.0", diff --git a/setup-jest.ts b/setup-jest.ts index dbb3d6b96..decf90381 100644 --- a/setup-jest.ts +++ b/setup-jest.ts @@ -31,8 +31,16 @@ class ResizeObserver { // eslint-disable-next-line @typescript-eslint/no-empty-function disconnect() {} } + Object.defineProperty(window, 'ResizeObserver', { writable: true, configurable: true, value: ResizeObserver, }); + +jest.mock('@newrelic/browser-agent/loaders/browser-agent', () => ({ + BrowserAgent: jest.fn().mockImplementation(() => ({ + start: jest.fn(), + stop: jest.fn(), + })), +})); diff --git a/src/app/app.config.ts b/src/app/app.config.ts index 0f53a5184..45474cd08 100644 --- a/src/app/app.config.ts +++ b/src/app/app.config.ts @@ -1,4 +1,3 @@ -import { withNgxsReduxDevtoolsPlugin } from '@ngxs/devtools-plugin'; import { provideStore } from '@ngxs/store'; import { TranslateModule } from '@ngx-translate/core'; @@ -49,7 +48,7 @@ export const appConfig: ApplicationConfig = { }), provideHttpClient(withInterceptors([authInterceptor, viewOnlyInterceptor, errorInterceptor])), provideRouter(routes, withInMemoryScrolling({ scrollPositionRestoration: 'top', anchorScrolling: 'enabled' })), - provideStore(STATES, withNgxsReduxDevtoolsPlugin({ disabled: true })), + provideStore(STATES), provideZoneChangeDetection({ eventCoalescing: true }), SENTRY_PROVIDER, ], diff --git a/src/app/app.routes.ts b/src/app/app.routes.ts index fefa6332e..0c2bb9259 100644 --- a/src/app/app.routes.ts +++ b/src/app/app.routes.ts @@ -3,17 +3,18 @@ import { provideStates } from '@ngxs/store'; import { Routes } from '@angular/router'; import { isFileGuard } from '@core/guards/is-file.guard'; -import { BookmarksState, ProjectsState } from '@shared/stores'; import { authGuard, redirectIfLoggedInGuard } from './core/guards'; import { isProjectGuard } from './core/guards/is-project.guard'; import { isRegistryGuard } from './core/guards/is-registry.guard'; -import { PreprintState } from './features/preprints/store/preprint'; +import { MyPreprintsState } from './features/preprints/store/my-preprints'; import { ProfileState } from './features/profile/store'; import { RegistriesState } from './features/registries/store'; import { LicensesHandlers, ProjectsHandlers, ProvidersHandlers } from './features/registries/store/handlers'; import { FilesHandlers } from './features/registries/store/handlers/files.handlers'; import { LicensesService } from './shared/services'; +import { BookmarksState } from './shared/stores/bookmarks'; +import { ProjectsState } from './shared/stores/projects'; export const routes: Routes = [ { @@ -89,7 +90,7 @@ export const routes: Routes = [ import('@osf/features/preprints/pages/my-preprints/my-preprints.component').then( (m) => m.MyPreprintsComponent ), - providers: [provideStates([PreprintState])], + providers: [provideStates([MyPreprintsState])], }, { path: 'preprints', diff --git a/src/app/core/components/nav-menu/nav-menu.component.spec.ts b/src/app/core/components/nav-menu/nav-menu.component.spec.ts index 2ab3830a4..26a43c3aa 100644 --- a/src/app/core/components/nav-menu/nav-menu.component.spec.ts +++ b/src/app/core/components/nav-menu/nav-menu.component.spec.ts @@ -8,7 +8,7 @@ import { CustomMenuItem } from '@osf/core/models'; import { AuthService } from '@osf/core/services'; import { ProviderSelectors } from '@osf/core/store/provider/provider.selectors'; import { UserSelectors } from '@osf/core/store/user/user.selectors'; -import { CurrentResourceSelectors } from '@osf/shared/stores'; +import { CurrentResourceSelectors } from '@osf/shared/stores/current-resource'; import { NavMenuComponent } from './nav-menu.component'; diff --git a/src/app/core/components/nav-menu/nav-menu.component.ts b/src/app/core/components/nav-menu/nav-menu.component.ts index 496ef7c72..6ac7d0bbd 100644 --- a/src/app/core/components/nav-menu/nav-menu.component.ts +++ b/src/app/core/components/nav-menu/nav-menu.component.ts @@ -20,7 +20,7 @@ import { IconComponent } from '@osf/shared/components'; import { CurrentResourceType, ReviewPermissions } from '@osf/shared/enums'; import { getViewOnlyParam } from '@osf/shared/helpers'; import { WrapFnPipe } from '@osf/shared/pipes'; -import { CurrentResourceSelectors, GetResourceDetails } from '@osf/shared/stores'; +import { CurrentResourceSelectors, GetResourceDetails } from '@osf/shared/stores/current-resource'; @Component({ selector: 'osf-nav-menu', diff --git a/src/app/core/constants/ngxs-states.constant.ts b/src/app/core/constants/ngxs-states.constant.ts index 409a3d258..08e715223 100644 --- a/src/app/core/constants/ngxs-states.constant.ts +++ b/src/app/core/constants/ngxs-states.constant.ts @@ -6,8 +6,11 @@ import { FilesState } from '@osf/features/files/store'; import { MetadataState } from '@osf/features/metadata/store'; import { ProjectOverviewState } from '@osf/features/project/overview/store'; import { RegistrationsState } from '@osf/features/project/registrations/store'; -import { AddonsState, CurrentResourceState, WikiState } from '@osf/shared/stores'; +import { AddonsState } from '@osf/shared/stores/addons'; import { BannersState } from '@osf/shared/stores/banners'; +import { ContributorsState } from '@osf/shared/stores/contributors'; +import { CurrentResourceState } from '@osf/shared/stores/current-resource'; +import { WikiState } from '@osf/shared/stores/wiki'; import { GlobalSearchState } from '@shared/stores/global-search'; import { InstitutionsState } from '@shared/stores/institutions'; import { InstitutionsSearchState } from '@shared/stores/institutions-search'; @@ -36,4 +39,5 @@ export const STATES = [ GlobalSearchState, BannersState, LinkedProjectsState, + ContributorsState, ]; diff --git a/src/app/core/guards/is-file.guard.ts b/src/app/core/guards/is-file.guard.ts index d94cfbdc3..c1e543139 100644 --- a/src/app/core/guards/is-file.guard.ts +++ b/src/app/core/guards/is-file.guard.ts @@ -5,8 +5,9 @@ import { map, switchMap } from 'rxjs/operators'; import { inject } from '@angular/core'; import { CanMatchFn, Route, Router, UrlSegment } from '@angular/router'; +import { CurrentResourceSelectors, GetResource } from '@osf/shared/stores/current-resource'; + import { CurrentResourceType } from '../../shared/enums'; -import { CurrentResourceSelectors, GetResource } from '../../shared/stores'; export const isFileGuard: CanMatchFn = (route: Route, segments: UrlSegment[]) => { const store = inject(Store); diff --git a/src/app/core/guards/is-project.guard.ts b/src/app/core/guards/is-project.guard.ts index d1eb1d6fb..43fbc078a 100644 --- a/src/app/core/guards/is-project.guard.ts +++ b/src/app/core/guards/is-project.guard.ts @@ -6,8 +6,8 @@ import { inject } from '@angular/core'; import { CanMatchFn, Route, Router, UrlSegment } from '@angular/router'; import { UserSelectors } from '@core/store/user'; +import { CurrentResourceSelectors, GetResource } from '@osf/shared/stores/current-resource'; import { CurrentResourceType } from '@shared/enums'; -import { CurrentResourceSelectors, GetResource } from '@shared/stores'; export const isProjectGuard: CanMatchFn = (route: Route, segments: UrlSegment[]) => { const store = inject(Store); diff --git a/src/app/core/guards/is-registry.guard.ts b/src/app/core/guards/is-registry.guard.ts index 63dfe4a28..ca713439e 100644 --- a/src/app/core/guards/is-registry.guard.ts +++ b/src/app/core/guards/is-registry.guard.ts @@ -7,7 +7,7 @@ import { CanMatchFn, Route, Router, UrlSegment } from '@angular/router'; import { UserSelectors } from '@core/store/user'; import { CurrentResourceType } from '@shared/enums'; -import { CurrentResourceSelectors, GetResource } from '@shared/stores'; +import { CurrentResourceSelectors, GetResource } from '@shared/stores/current-resource'; export const isRegistryGuard: CanMatchFn = (route: Route, segments: UrlSegment[]) => { const store = inject(Store); diff --git a/src/app/core/provider/application.initialization.provider.spec.ts b/src/app/core/provider/application.initialization.provider.spec.ts index 12b73f067..feaa49742 100644 --- a/src/app/core/provider/application.initialization.provider.spec.ts +++ b/src/app/core/provider/application.initialization.provider.spec.ts @@ -7,7 +7,9 @@ import { OSFConfigService } from '@core/services/osf-config.service'; import { initializeApplication } from './application.initialization.provider'; import { ENVIRONMENT } from './environment.provider'; +import { BrowserAgent } from '@newrelic/browser-agent/loaders/browser-agent'; import * as Sentry from '@sentry/angular'; +import { NEW_RELIC_CONFIG_MOCK } from '@testing/mocks/new-relic.mock'; import { OSFTestingModule } from '@testing/osf.testing.module'; import { GoogleTagManagerConfiguration } from 'angular-google-tag-manager'; @@ -92,4 +94,28 @@ describe('Provider: sentry', () => { expect(googleTagManagerConfigurationMock.set).not.toHaveBeenCalled(); expect(httpMock.verify).toBeTruthy(); }); + + it('should initialize New Relic if enabled', async () => { + const environment = TestBed.inject(ENVIRONMENT); + Object.assign(environment, NEW_RELIC_CONFIG_MOCK); + + await runInInjectionContext(TestBed, async () => { + await initializeApplication()(); + }); + + expect(BrowserAgent).toHaveBeenCalledTimes(1); + expect(httpMock.verify).toBeTruthy(); + }); + + it('should not initialize New Relic if disabled', async () => { + const environment = TestBed.inject(ENVIRONMENT); + environment.newRelicEnabled = false; + + await runInInjectionContext(TestBed, async () => { + await initializeApplication()(); + }); + + expect(BrowserAgent).not.toHaveBeenCalled(); + expect(httpMock.verify).toBeTruthy(); + }); }); diff --git a/src/app/core/provider/application.initialization.provider.ts b/src/app/core/provider/application.initialization.provider.ts index 137ab79e7..b022860e9 100644 --- a/src/app/core/provider/application.initialization.provider.ts +++ b/src/app/core/provider/application.initialization.provider.ts @@ -63,7 +63,7 @@ export function initializeApplication() { loader_config: { accountID: environment.newRelicLoaderConfigAccountID, trustKey: environment.newRelicLoaderConfigTrustKey, - agentID: environment.newRelicLoaderConfigAgengID, + agentID: environment.newRelicLoaderConfigAgentID, licenseKey: environment.newRelicLoaderConfigLicenseKey, applicationID: environment.newRelicLoaderConfigApplicationID, }, diff --git a/src/app/core/store/provider/provider.selectors.ts b/src/app/core/store/provider/provider.selectors.ts index c63f3b537..1da68985e 100644 --- a/src/app/core/store/provider/provider.selectors.ts +++ b/src/app/core/store/provider/provider.selectors.ts @@ -1,5 +1,6 @@ import { Selector } from '@ngxs/store'; +import { ReviewPermissions } from '@osf/shared/enums'; import { ProviderShortInfoModel } from '@osf/shared/models'; import { ProviderStateModel } from './provider.model'; @@ -10,4 +11,12 @@ export class ProviderSelectors { static getCurrentProvider(state: ProviderStateModel): ProviderShortInfoModel | null { return state.currentProvider; } + + @Selector([ProviderState]) + static hasAdminAccess(state: ProviderStateModel): boolean { + return ( + state.currentProvider?.permissions?.some((permission) => permission === ReviewPermissions.SetUpModeration) || + false + ); + } } diff --git a/src/app/features/analytics/components/view-duplicates/view-duplicates.component.spec.ts b/src/app/features/analytics/components/view-duplicates/view-duplicates.component.spec.ts index dbec46a29..fbdec8174 100644 --- a/src/app/features/analytics/components/view-duplicates/view-duplicates.component.spec.ts +++ b/src/app/features/analytics/components/view-duplicates/view-duplicates.component.spec.ts @@ -10,7 +10,7 @@ import { ActivatedRoute, Router } from '@angular/router'; import { ProjectOverviewSelectors } from '@osf/features/project/overview/store'; import { RegistryOverviewSelectors } from '@osf/features/registry/store/registry-overview'; import { ResourceType } from '@osf/shared/enums'; -import { DuplicatesSelectors } from '@osf/shared/stores'; +import { DuplicatesSelectors } from '@osf/shared/stores/duplicates'; import { ContributorsListComponent, CustomPaginatorComponent, diff --git a/src/app/features/analytics/components/view-duplicates/view-duplicates.component.ts b/src/app/features/analytics/components/view-duplicates/view-duplicates.component.ts index 176e26edd..519453b58 100644 --- a/src/app/features/analytics/components/view-duplicates/view-duplicates.component.ts +++ b/src/app/features/analytics/components/view-duplicates/view-duplicates.component.ts @@ -41,7 +41,8 @@ import { import { ResourceType, UserPermissions } from '@osf/shared/enums'; import { BaseNodeModel, ToolbarResource } from '@osf/shared/models'; import { CustomDialogService, LoaderService } from '@osf/shared/services'; -import { ClearDuplicates, DuplicatesSelectors, GetAllDuplicates, GetResourceWithChildren } from '@osf/shared/stores'; +import { GetResourceWithChildren } from '@osf/shared/stores/current-resource'; +import { ClearDuplicates, DuplicatesSelectors, GetAllDuplicates } from '@osf/shared/stores/duplicates'; @Component({ selector: 'osf-view-duplicates', diff --git a/src/app/features/collections/collections.routes.ts b/src/app/features/collections/collections.routes.ts index cb95bfe26..bbdeac48a 100644 --- a/src/app/features/collections/collections.routes.ts +++ b/src/app/features/collections/collections.routes.ts @@ -6,15 +6,12 @@ import { authGuard } from '@osf/core/guards'; import { AddToCollectionState } from '@osf/features/collections/store/add-to-collection'; import { CollectionsModerationState } from '@osf/features/moderation/store/collections-moderation'; import { ConfirmLeavingGuard } from '@shared/guards'; -import { - BookmarksState, - CitationsState, - ContributorsState, - NodeLinksState, - ProjectsState, - SubjectsState, -} from '@shared/stores'; +import { BookmarksState } from '@shared/stores/bookmarks'; +import { CitationsState } from '@shared/stores/citations'; import { CollectionsState } from '@shared/stores/collections'; +import { NodeLinksState } from '@shared/stores/node-links'; +import { ProjectsState } from '@shared/stores/projects'; +import { SubjectsState } from '@shared/stores/subjects'; export const collectionsRoutes: Routes = [ { @@ -47,7 +44,7 @@ export const collectionsRoutes: Routes = [ import('@osf/features/collections/components/add-to-collection/add-to-collection.component').then( (mod) => mod.AddToCollectionComponent ), - providers: [provideStates([ProjectsState, CollectionsState, AddToCollectionState, ContributorsState])], + providers: [provideStates([ProjectsState, CollectionsState, AddToCollectionState])], canActivate: [authGuard], canDeactivate: [ConfirmLeavingGuard], }, diff --git a/src/app/features/collections/components/add-to-collection/add-to-collection.component.html b/src/app/features/collections/components/add-to-collection/add-to-collection.component.html index 204620fa5..e60b9c227 100644 --- a/src/app/features/collections/components/add-to-collection/add-to-collection.component.html +++ b/src/app/features/collections/components/add-to-collection/add-to-collection.component.html @@ -32,6 +32,7 @@

{{ collectionProvider()? /> {{ 'collections.addToCollection.projectContributors' | translate }}

[(contributors)]="projectContributors" [tableParams]="tableParams()" [isLoading]="isContributorsLoading()" + [isLoadingMore]="isLoadingMore()" (remove)="handleRemoveContributor($event)" + (loadMore)="loadMoreContributors()" >
diff --git a/src/app/features/collections/components/add-to-collection/project-contributors-step/project-contributors-step.component.ts b/src/app/features/collections/components/add-to-collection/project-contributors-step/project-contributors-step.component.ts index f6518e2d4..d532a621c 100644 --- a/src/app/features/collections/components/add-to-collection/project-contributors-step/project-contributors-step.component.ts +++ b/src/app/features/collections/components/add-to-collection/project-contributors-step/project-contributors-step.component.ts @@ -40,8 +40,9 @@ import { BulkUpdateContributors, ContributorsSelectors, DeleteContributor, - ProjectsSelectors, -} from '@osf/shared/stores'; + LoadMoreContributors, +} from '@osf/shared/stores/contributors'; +import { ProjectsSelectors } from '@osf/shared/stores/projects'; @Component({ selector: 'osf-project-contributors-step', @@ -61,20 +62,26 @@ export class ProjectContributorsStepComponent { readonly contributorsTotalCount = select(ContributorsSelectors.getContributorsTotalCount); readonly selectedProject = select(ProjectsSelectors.getSelectedProject); readonly currentUser = select(UserSelectors.getCurrentUser); + isLoadingMore = select(ContributorsSelectors.isContributorsLoadingMore); private initialContributors = select(ContributorsSelectors.getContributors); readonly projectContributors = signal([]); + pageSize = select(ContributorsSelectors.getContributorsPageSize); readonly tableParams = computed(() => ({ ...DEFAULT_TABLE_PARAMS, totalRecords: this.contributorsTotalCount(), - paginator: this.contributorsTotalCount() > DEFAULT_TABLE_PARAMS.rows, + paginator: false, + scrollable: true, + firstRowIndex: 0, + rows: this.pageSize(), })); stepperActiveValue = input.required(); targetStepValue = input.required(); isDisabled = input.required(); isProjectMetadataSaved = input(false); + projectId = input(); stepChange = output(); contributorsSaved = output(); @@ -84,6 +91,7 @@ export class ProjectContributorsStepComponent { bulkAddContributors: BulkAddContributors, bulkUpdateContributors: BulkUpdateContributors, deleteContributor: DeleteContributor, + loadMoreContributors: LoadMoreContributors, }); constructor() { @@ -150,14 +158,15 @@ export class ProjectContributorsStepComponent { this.stepChange.emit(this.targetStepValue()); } - private openAddContributorDialog() { - const addedContributorIds = this.projectContributors().map((x) => x.userId); + loadMoreContributors(): void { + this.actions.loadMoreContributors(this.projectId(), ResourceType.Project); + } + private openAddContributorDialog() { this.customDialogService .open(AddContributorDialogComponent, { header: 'project.contributors.addDialog.addRegisteredContributor', width: '448px', - data: addedContributorIds, }) .onClose.pipe( filter((res: ContributorDialogAddModel) => !!res), diff --git a/src/app/features/collections/components/add-to-collection/project-metadata-step/project-metadata-step.component.html b/src/app/features/collections/components/add-to-collection/project-metadata-step/project-metadata-step.component.html index a9c4d29d1..e7193524d 100644 --- a/src/app/features/collections/components/add-to-collection/project-metadata-step/project-metadata-step.component.html +++ b/src/app/features/collections/components/add-to-collection/project-metadata-step/project-metadata-step.component.html @@ -20,7 +20,7 @@

{{ 'collections.addToCollection.resourceMetadata' | translate }}

-

{{ 'collections.addToCollection.form.license' | translate }}

+

{{ 'common.labels.license' | translate }}

{{ projectLicense()?.name || 'collections.addToCollection.noLicense' | translate }}

diff --git a/src/app/features/collections/components/add-to-collection/project-metadata-step/project-metadata-step.component.ts b/src/app/features/collections/components/add-to-collection/project-metadata-step/project-metadata-step.component.ts index a9df80864..47c2a3862 100644 --- a/src/app/features/collections/components/add-to-collection/project-metadata-step/project-metadata-step.component.ts +++ b/src/app/features/collections/components/add-to-collection/project-metadata-step/project-metadata-step.component.ts @@ -40,7 +40,8 @@ import { LicenseModel } from '@shared/models'; import { ProjectModel } from '@shared/models/projects'; import { InterpolatePipe } from '@shared/pipes'; import { ToastService } from '@shared/services'; -import { ClearProjects, GetAllContributors, UpdateProjectMetadata } from '@shared/stores'; +import { GetAllContributors } from '@shared/stores/contributors'; +import { ClearProjects, UpdateProjectMetadata } from '@shared/stores/projects'; import { ProjectsSelectors } from '@shared/stores/projects/projects.selectors'; @Component({ diff --git a/src/app/features/collections/components/add-to-collection/select-project-step/select-project-step.component.spec.ts b/src/app/features/collections/components/add-to-collection/select-project-step/select-project-step.component.spec.ts index dcd0917d5..237d3c88d 100644 --- a/src/app/features/collections/components/add-to-collection/select-project-step/select-project-step.component.spec.ts +++ b/src/app/features/collections/components/add-to-collection/select-project-step/select-project-step.component.spec.ts @@ -6,7 +6,7 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; import { ProjectSelectorComponent } from '@shared/components'; import { ToastService } from '@shared/services'; -import { CollectionsSelectors } from '@shared/stores'; +import { CollectionsSelectors } from '@shared/stores/collections'; import { ProjectsSelectors } from '@shared/stores/projects/projects.selectors'; import { SelectProjectStepComponent } from './select-project-step.component'; diff --git a/src/app/features/collections/components/add-to-collection/select-project-step/select-project-step.component.ts b/src/app/features/collections/components/add-to-collection/select-project-step/select-project-step.component.ts index 78d9953c3..bef7c72c5 100644 --- a/src/app/features/collections/components/add-to-collection/select-project-step/select-project-step.component.ts +++ b/src/app/features/collections/components/add-to-collection/select-project-step/select-project-step.component.ts @@ -8,7 +8,7 @@ import { Step, StepItem, StepPanel } from 'primeng/stepper'; import { ChangeDetectionStrategy, Component, computed, input, output, signal } from '@angular/core'; import { AddToCollectionSteps } from '@osf/features/collections/enums'; -import { SetSelectedProject } from '@osf/shared/stores'; +import { SetSelectedProject } from '@osf/shared/stores/projects'; import { ProjectSelectorComponent } from '@shared/components'; import { ProjectModel } from '@shared/models/projects'; import { CollectionsSelectors, GetUserCollectionSubmissions } from '@shared/stores/collections'; diff --git a/src/app/features/collections/components/collections-discover/collections-discover.component.spec.ts b/src/app/features/collections/components/collections-discover/collections-discover.component.spec.ts index 3f2ad5eeb..27c54b634 100644 --- a/src/app/features/collections/components/collections-discover/collections-discover.component.spec.ts +++ b/src/app/features/collections/components/collections-discover/collections-discover.component.spec.ts @@ -7,7 +7,7 @@ import { SENTRY_TOKEN } from '@core/provider/sentry.provider'; import { CollectionsMainContentComponent } from '@osf/features/collections/components'; import { LoadingSpinnerComponent, SearchInputComponent } from '@shared/components'; import { CustomDialogService, ToastService } from '@shared/services'; -import { CollectionsSelectors } from '@shared/stores'; +import { CollectionsSelectors } from '@shared/stores/collections'; import { CollectionsDiscoverComponent } from './collections-discover.component'; diff --git a/src/app/features/collections/components/collections-discover/collections-discover.component.ts b/src/app/features/collections/components/collections-discover/collections-discover.component.ts index 77a940823..80a7bf084 100644 --- a/src/app/features/collections/components/collections-discover/collections-discover.component.ts +++ b/src/app/features/collections/components/collections-discover/collections-discover.component.ts @@ -25,7 +25,7 @@ import { SearchCollectionSubmissions, SetPageNumber, SetSearchValue, -} from '@osf/shared/stores'; +} from '@osf/shared/stores/collections'; import { CollectionsQuerySyncService } from '../../services'; import { CollectionsHelpDialogComponent } from '../collections-help-dialog/collections-help-dialog.component'; diff --git a/src/app/features/contributors/components/create-view-link-dialog/create-view-link-dialog.component.spec.ts b/src/app/features/contributors/components/create-view-link-dialog/create-view-link-dialog.component.spec.ts index e7d9849a2..688c92f82 100644 --- a/src/app/features/contributors/components/create-view-link-dialog/create-view-link-dialog.component.spec.ts +++ b/src/app/features/contributors/components/create-view-link-dialog/create-view-link-dialog.component.spec.ts @@ -4,7 +4,7 @@ import { DynamicDialogConfig, DynamicDialogRef } from 'primeng/dynamicdialog'; import { ComponentFixture, TestBed } from '@angular/core/testing'; -import { CurrentResourceSelectors } from '@osf/shared/stores'; +import { CurrentResourceSelectors } from '@osf/shared/stores/current-resource'; import { CreateViewLinkDialogComponent } from './create-view-link-dialog.component'; diff --git a/src/app/features/contributors/components/create-view-link-dialog/create-view-link-dialog.component.ts b/src/app/features/contributors/components/create-view-link-dialog/create-view-link-dialog.component.ts index f6c0c68b8..e3c1b4d75 100644 --- a/src/app/features/contributors/components/create-view-link-dialog/create-view-link-dialog.component.ts +++ b/src/app/features/contributors/components/create-view-link-dialog/create-view-link-dialog.component.ts @@ -13,7 +13,7 @@ import { ComponentCheckboxItemComponent, LoadingSpinnerComponent, TextInputCompo import { InputLimits } from '@osf/shared/constants'; import { CustomValidators } from '@osf/shared/helpers'; import { ComponentCheckboxItemModel } from '@osf/shared/models'; -import { CurrentResourceSelectors, GetResourceWithChildren } from '@osf/shared/stores'; +import { CurrentResourceSelectors, GetResourceWithChildren } from '@osf/shared/stores/current-resource'; import { ResourceInfoModel, ViewOnlyLinkComponentItem } from '../../models'; diff --git a/src/app/features/contributors/contributors.component.html b/src/app/features/contributors/contributors.component.html index dd3d74d45..3a5785bee 100644 --- a/src/app/features/contributors/contributors.component.html +++ b/src/app/features/contributors/contributors.component.html @@ -63,6 +63,7 @@

{{ 'navigation.contributors' | translate } class="w-full" [(contributors)]="contributors" [isLoading]="isContributorsLoading()" + [isLoadingMore]="isLoadingMore()" [tableParams]="tableParams()" [hasAdminAccess]="hasAdminAccess()" [currentUserId]="currentUser()?.id" @@ -70,7 +71,7 @@

{{ 'navigation.contributors' | translate } [showInfo]="true" [resourceType]="resourceType()" (remove)="removeContributor($event)" - (pageChanged)="pageChanged($event)" + (loadMore)="loadMoreContributors()" > @if (hasChanges) { diff --git a/src/app/features/contributors/contributors.component.spec.ts b/src/app/features/contributors/contributors.component.spec.ts index 34f3afa23..4831001a0 100644 --- a/src/app/features/contributors/contributors.component.spec.ts +++ b/src/app/features/contributors/contributors.component.spec.ts @@ -10,7 +10,9 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; import { ContributorPermission } from '@shared/enums'; import { ContributorModel } from '@shared/models'; import { CustomConfirmationService } from '@shared/services'; -import { ContributorsSelectors, CurrentResourceSelectors, ViewOnlyLinkSelectors } from '@shared/stores'; +import { ContributorsSelectors } from '@shared/stores/contributors'; +import { CurrentResourceSelectors } from '@shared/stores/current-resource'; +import { ViewOnlyLinkSelectors } from '@shared/stores/view-only-links'; import { ContributorsComponent } from './contributors.component'; diff --git a/src/app/features/contributors/contributors.component.ts b/src/app/features/contributors/contributors.component.ts index 25754e14a..5f620123e 100644 --- a/src/app/features/contributors/contributors.component.ts +++ b/src/app/features/contributors/contributors.component.ts @@ -4,7 +4,7 @@ import { TranslatePipe } from '@ngx-translate/core'; import { Button } from 'primeng/button'; import { Select } from 'primeng/select'; -import { TableModule, TablePageEvent } from 'primeng/table'; +import { TableModule } from 'primeng/table'; import { debounceTime, distinctUntilChanged, filter, map, of, switchMap } from 'rxjs'; @@ -15,6 +15,7 @@ import { DestroyRef, effect, inject, + OnDestroy, OnInit, signal, } from '@angular/core'; @@ -49,23 +50,30 @@ import { AcceptRequestAccess, AddContributor, BulkAddContributors, + BulkAddContributorsFromParentProject, BulkUpdateContributors, ContributorsSelectors, - CreateViewOnlyLink, - CurrentResourceSelectors, DeleteContributor, - DeleteViewOnlyLink, - FetchViewOnlyLinks, GetAllContributors, GetRequestAccessContributors, - GetResourceDetails, - GetResourceWithChildren, + LoadMoreContributors, RejectRequestAccess, + ResetContributorsState, UpdateBibliographyFilter, UpdateContributorsSearchValue, UpdatePermissionFilter, +} from '@osf/shared/stores/contributors'; +import { + CurrentResourceSelectors, + GetResourceDetails, + GetResourceWithChildren, +} from '@osf/shared/stores/current-resource'; +import { + CreateViewOnlyLink, + DeleteViewOnlyLink, + FetchViewOnlyLinks, ViewOnlyLinkSelectors, -} from '@osf/shared/stores'; +} from '@osf/shared/stores/view-only-links'; import { CreateViewLinkDialogComponent } from './components'; import { ResourceInfoModel } from './models'; @@ -87,7 +95,7 @@ import { ResourceInfoModel } from './models'; styleUrl: './contributors.component.scss', changeDetection: ChangeDetectionStrategy.OnPush, }) -export class ContributorsComponent implements OnInit { +export class ContributorsComponent implements OnInit, OnDestroy { searchControl = new FormControl(''); readonly destroyRef = inject(DestroyRef); @@ -123,14 +131,15 @@ export class ContributorsComponent implements OnInit { readonly hasAdminAccess = select(CurrentResourceSelectors.hasResourceAdminAccess); readonly resourceAccessRequestEnabled = select(CurrentResourceSelectors.resourceAccessRequestEnabled); readonly currentUser = select(UserSelectors.getCurrentUser); - page = select(ContributorsSelectors.getContributorsPageNumber); pageSize = select(ContributorsSelectors.getContributorsPageSize); + isLoadingMore = select(ContributorsSelectors.isContributorsLoadingMore); readonly tableParams = computed(() => ({ ...DEFAULT_TABLE_PARAMS, totalRecords: this.contributorsTotalCount(), - paginator: this.contributorsTotalCount() > DEFAULT_TABLE_PARAMS.rows, - firstRowIndex: (this.page() - 1) * this.pageSize(), + paginator: false, + scrollable: true, + firstRowIndex: 0, rows: this.pageSize(), })); @@ -153,12 +162,14 @@ export class ContributorsComponent implements OnInit { getViewOnlyLinks: FetchViewOnlyLinks, getResourceDetails: GetResourceDetails, getContributors: GetAllContributors, + loadMoreContributors: LoadMoreContributors, updateSearchValue: UpdateContributorsSearchValue, updatePermissionFilter: UpdatePermissionFilter, updateBibliographyFilter: UpdateBibliographyFilter, deleteContributor: DeleteContributor, bulkUpdateContributors: BulkUpdateContributors, bulkAddContributors: BulkAddContributors, + bulkAddContributorsFromParentProject: BulkAddContributorsFromParentProject, addContributor: AddContributor, createViewOnlyLink: CreateViewOnlyLink, deleteViewOnlyLink: DeleteViewOnlyLink, @@ -166,6 +177,7 @@ export class ContributorsComponent implements OnInit { acceptRequestAccess: AcceptRequestAccess, rejectRequestAccess: RejectRequestAccess, getResourceWithChildren: GetResourceWithChildren, + resetContributorsState: ResetContributorsState, }); get hasChanges(): boolean { @@ -187,6 +199,10 @@ export class ContributorsComponent implements OnInit { this.setSearchSubscription(); } + ngOnDestroy(): void { + this.actions.resetContributorsState(); + } + private setSearchSubscription() { this.searchControl.valueChanges .pipe(debounceTime(500), distinctUntilChanged(), takeUntilDestroyed(this.destroyRef)) @@ -245,27 +261,32 @@ export class ContributorsComponent implements OnInit { } openAddContributorDialog() { - const addedContributorIds = this.initialContributors().map((x) => x.userId); - const rootParentId = this.resourceDetails().rootParentId ?? this.resourceId(); + const resourceDetails = this.resourceDetails(); + const resourceId = this.resourceId(); + const rootParentId = resourceDetails.rootParentId ?? resourceId; this.loaderService.show(); this.actions - .getResourceWithChildren(rootParentId, this.resourceId(), this.resourceType()) + .getResourceWithChildren(rootParentId, resourceId, this.resourceType()) .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(() => { this.loaderService.hide(); - const components = this.mapNodesToComponentCheckboxItems(this.resourceChildren(), this.resourceId()); + const components = this.mapNodesToComponentCheckboxItems(this.resourceChildren(), resourceId); this.customDialogService .open(AddContributorDialogComponent, { header: 'project.contributors.addDialog.addRegisteredContributor', width: '448px', data: { - addedContributorIds, components, - resourceName: this.resourceDetails().title, + resourceName: resourceDetails.title, + parentResourceName: resourceDetails.parent?.title, + allowAddingContributorsFromParentProject: + this.resourceType() === ResourceType.Project && + resourceDetails.rootParentId && + resourceDetails.rootParentId !== resourceId, }, }) .onClose.pipe( @@ -273,7 +294,9 @@ export class ContributorsComponent implements OnInit { takeUntilDestroyed(this.destroyRef) ) .subscribe((res: ContributorDialogAddModel) => { - if (res.type === AddContributorType.Unregistered) { + if (res.type === AddContributorType.ParentProject) { + this.addContributorsFromParentProjectToComponents(); + } else if (res.type === AddContributorType.Unregistered) { this.openAddUnregisteredContributorDialog(); } else { this.addContributorsToComponents(res); @@ -303,6 +326,13 @@ export class ContributorsComponent implements OnInit { .subscribe(() => this.toastService.showSuccess('project.contributors.toastMessages.multipleAddSuccessMessage')); } + private addContributorsFromParentProjectToComponents(): void { + this.actions + .bulkAddContributorsFromParentProject(this.resourceId(), this.resourceType()) + .pipe(takeUntilDestroyed(this.destroyRef)) + .subscribe(() => this.toastService.showSuccess('project.contributors.toastMessages.multipleAddSuccessMessage')); + } + openAddUnregisteredContributorDialog() { this.customDialogService .open(AddUnregisteredContributorDialogComponent, { @@ -383,11 +413,8 @@ export class ContributorsComponent implements OnInit { }); } - pageChanged(event: TablePageEvent) { - const page = Math.floor(event.first / event.rows) + 1; - const pageSize = event.rows; - - this.actions.getContributors(this.resourceId(), this.resourceType(), page, pageSize); + loadMoreContributors(): void { + this.actions.loadMoreContributors(this.resourceId(), this.resourceType()); } createViewLink() { diff --git a/src/app/features/files/components/move-file-dialog/move-file-dialog.component.spec.ts b/src/app/features/files/components/move-file-dialog/move-file-dialog.component.spec.ts index e329e8643..97b2e0ca8 100644 --- a/src/app/features/files/components/move-file-dialog/move-file-dialog.component.spec.ts +++ b/src/app/features/files/components/move-file-dialog/move-file-dialog.component.spec.ts @@ -7,7 +7,7 @@ import { signal } from '@angular/core'; import { ComponentFixture, TestBed } from '@angular/core/testing'; import { CustomConfirmationService, FilesService, ToastService } from '@osf/shared/services'; -import { CurrentResourceSelectors } from '@shared/stores'; +import { CurrentResourceSelectors } from '@shared/stores/current-resource'; import { FilesSelectors } from '../../store'; diff --git a/src/app/features/files/components/move-file-dialog/move-file-dialog.component.ts b/src/app/features/files/components/move-file-dialog/move-file-dialog.component.ts index 6b7016433..40c0f12bc 100644 --- a/src/app/features/files/components/move-file-dialog/move-file-dialog.component.ts +++ b/src/app/features/files/components/move-file-dialog/move-file-dialog.component.ts @@ -23,7 +23,11 @@ import { import { FileKind, ResourceType, SupportedFeature } from '@osf/shared/enums'; import { FilesMapper } from '@osf/shared/mappers/files/files.mapper'; import { FileFolderModel, FileModel } from '@osf/shared/models'; -import { CurrentResourceSelectors, GetResourceDetails, GetResourceWithChildren } from '@osf/shared/stores'; +import { + CurrentResourceSelectors, + GetResourceDetails, + GetResourceWithChildren, +} from '@osf/shared/stores/current-resource'; import { FileSelectDestinationComponent, IconComponent, LoadingSpinnerComponent } from '@shared/components'; import { CustomConfirmationService, FilesService, ToastService } from '@shared/services'; @@ -182,6 +186,8 @@ export class MoveFileDialogComponent { } this.isFilesUpdating.set(true); + const headerKey = this.isMoveAction ? 'files.dialogs.moveFile.movingHeader' : 'files.dialogs.moveFile.copingHeader'; + this.config.header = this.translateService.instant(headerKey); const action = this.config.data.action; const files: FileModel[] = this.config.data.files; const totalFiles = files.length; @@ -209,6 +215,7 @@ export class MoveFileDialogComponent { this.openReplaceMoveDialog(conflictFiles, path, action); } else { this.showToast(action); + this.config.header = this.translateService.instant('files.dialogs.moveFile.title'); this.completeMove(); } } diff --git a/src/app/features/files/pages/files/files.component.spec.ts b/src/app/features/files/pages/files/files.component.spec.ts index 1c2955d04..7abc3d793 100644 --- a/src/app/features/files/pages/files/files.component.spec.ts +++ b/src/app/features/files/pages/files/files.component.spec.ts @@ -21,7 +21,7 @@ import { ViewOnlyLinkMessageComponent, } from '@osf/shared/components'; import { CustomConfirmationService, FilesService } from '@osf/shared/services'; -import { CurrentResourceSelectors } from '@osf/shared/stores'; +import { CurrentResourceSelectors } from '@osf/shared/stores/current-resource'; import { GoogleFilePickerComponent } from '@shared/components/google-file-picker/google-file-picker.component'; import { FilesSelectors } from '../../store'; diff --git a/src/app/features/files/pages/files/files.component.ts b/src/app/features/files/pages/files/files.component.ts index 431277fc1..28bde3f50 100644 --- a/src/app/features/files/pages/files/files.component.ts +++ b/src/app/features/files/pages/files/files.component.ts @@ -45,7 +45,7 @@ import { import { ALL_SORT_OPTIONS, FILE_SIZE_LIMIT } from '@osf/shared/constants'; import { FileMenuType, ResourceType, SupportedFeature, UserPermissions } from '@osf/shared/enums'; import { getViewOnlyParamFromUrl, hasViewOnlyParam } from '@osf/shared/helpers'; -import { CurrentResourceSelectors, GetResourceDetails } from '@osf/shared/stores'; +import { CurrentResourceSelectors, GetResourceDetails } from '@osf/shared/stores/current-resource'; import { FilesTreeComponent, FileUploadDialogComponent, diff --git a/src/app/features/home/pages/dashboard/dashboard.component.spec.ts b/src/app/features/home/pages/dashboard/dashboard.component.spec.ts index 7a0213dac..005ddcf6d 100644 --- a/src/app/features/home/pages/dashboard/dashboard.component.spec.ts +++ b/src/app/features/home/pages/dashboard/dashboard.component.spec.ts @@ -13,7 +13,7 @@ import { SearchInputComponent, SubHeaderComponent, } from '@shared/components'; -import { MyResourcesSelectors } from '@shared/stores'; +import { MyResourcesSelectors } from '@shared/stores/my-resources'; import { DashboardComponent } from './dashboard.component'; diff --git a/src/app/features/home/pages/dashboard/dashboard.component.ts b/src/app/features/home/pages/dashboard/dashboard.component.ts index 54aa5bba9..8a04adc12 100644 --- a/src/app/features/home/pages/dashboard/dashboard.component.ts +++ b/src/app/features/home/pages/dashboard/dashboard.component.ts @@ -26,7 +26,7 @@ import { DEFAULT_TABLE_PARAMS } from '@osf/shared/constants'; import { SortOrder } from '@osf/shared/enums'; import { MyResourcesItem, MyResourcesSearchFilters, TableParameters } from '@osf/shared/models'; import { CustomDialogService, ProjectRedirectDialogService } from '@osf/shared/services'; -import { ClearMyResources, GetMyProjects, MyResourcesSelectors } from '@osf/shared/stores'; +import { ClearMyResources, GetMyProjects, MyResourcesSelectors } from '@osf/shared/stores/my-resources'; @Component({ selector: 'osf-dashboard', diff --git a/src/app/features/institutions/pages/institutions-list/institutions-list.component.ts b/src/app/features/institutions/pages/institutions-list/institutions-list.component.ts index 17bbdac17..4b1fefc08 100644 --- a/src/app/features/institutions/pages/institutions-list/institutions-list.component.ts +++ b/src/app/features/institutions/pages/institutions-list/institutions-list.component.ts @@ -12,7 +12,7 @@ import { ActivatedRoute, Router, RouterLink } from '@angular/router'; import { ScheduledBannerComponent } from '@core/components/osf-banners/scheduled-banner/scheduled-banner.component'; import { LoadingSpinnerComponent, SearchInputComponent, SubHeaderComponent } from '@osf/shared/components'; -import { FetchInstitutions, InstitutionsSelectors } from '@osf/shared/stores'; +import { FetchInstitutions, InstitutionsSelectors } from '@osf/shared/stores/institutions'; @Component({ selector: 'osf-institutions-list', diff --git a/src/app/features/metadata/components/cedar-template-form/cedar-template-form.component.ts b/src/app/features/metadata/components/cedar-template-form/cedar-template-form.component.ts index d5af60476..16a562e69 100644 --- a/src/app/features/metadata/components/cedar-template-form/cedar-template-form.component.ts +++ b/src/app/features/metadata/components/cedar-template-form/cedar-template-form.component.ts @@ -24,16 +24,18 @@ import { toSignal } from '@angular/core/rxjs-interop'; import { ActivatedRoute } from '@angular/router'; import { ENVIRONMENT } from '@core/provider/environment.provider'; -import { CEDAR_CONFIG, CEDAR_VIEWER_CONFIG } from '@osf/features/metadata/constants'; -import { CedarMetadataHelper } from '@osf/features/metadata/helpers'; + +import 'cedar-artifact-viewer'; +import 'cedar-embeddable-editor'; + +import { CEDAR_CONFIG, CEDAR_VIEWER_CONFIG } from '../../constants'; +import { CedarMetadataHelper } from '../../helpers'; import { CedarEditorElement, CedarMetadataDataTemplateJsonApi, CedarMetadataRecordData, CedarRecordDataBinding, -} from '@osf/features/metadata/models'; - -import 'cedar-artifact-viewer'; +} from '../../models'; @Component({ selector: 'osf-cedar-template-form', diff --git a/src/app/features/metadata/components/metadata-contributors/metadata-contributors.component.html b/src/app/features/metadata/components/metadata-contributors/metadata-contributors.component.html index e7956426c..3ec6df984 100644 --- a/src/app/features/metadata/components/metadata-contributors/metadata-contributors.component.html +++ b/src/app/features/metadata/components/metadata-contributors/metadata-contributors.component.html @@ -12,9 +12,14 @@

{{ 'project.overview.metadata.contributors' | translate }}

}

- @if (contributors()) { -
- + @if (contributors().length) { +
+
} diff --git a/src/app/features/metadata/components/metadata-contributors/metadata-contributors.component.ts b/src/app/features/metadata/components/metadata-contributors/metadata-contributors.component.ts index 461edea00..360c4b225 100644 --- a/src/app/features/metadata/components/metadata-contributors/metadata-contributors.component.ts +++ b/src/app/features/metadata/components/metadata-contributors/metadata-contributors.component.ts @@ -15,7 +15,11 @@ import { ContributorModel } from '@osf/shared/models'; changeDetection: ChangeDetectionStrategy.OnPush, }) export class MetadataContributorsComponent { - openEditContributorDialog = output(); contributors = input([]); + isLoading = input(false); + hasMoreContributors = input(false); readonly = input(false); + + openEditContributorDialog = output(); + loadMoreContributors = output(); } diff --git a/src/app/features/metadata/components/metadata-license/metadata-license.component.html b/src/app/features/metadata/components/metadata-license/metadata-license.component.html index 092b81ff8..a4207154f 100644 --- a/src/app/features/metadata/components/metadata-license/metadata-license.component.html +++ b/src/app/features/metadata/components/metadata-license/metadata-license.component.html @@ -1,6 +1,6 @@
-

{{ 'project.overview.metadata.license' | translate }}

+

{{ 'common.labels.license' | translate }}

@if (!readonly()) { @if (hasChanges) { diff --git a/src/app/features/metadata/dialogs/contributors-dialog/contributors-dialog.component.spec.ts b/src/app/features/metadata/dialogs/contributors-dialog/contributors-dialog.component.spec.ts index 448901f5a..8f1fa353e 100644 --- a/src/app/features/metadata/dialogs/contributors-dialog/contributors-dialog.component.spec.ts +++ b/src/app/features/metadata/dialogs/contributors-dialog/contributors-dialog.component.spec.ts @@ -6,7 +6,7 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; import { ContributorModel } from '@osf/shared/models'; import { CustomDialogService } from '@osf/shared/services'; -import { ContributorsSelectors } from '@osf/shared/stores'; +import { ContributorsSelectors } from '@osf/shared/stores/contributors'; import { SearchInputComponent } from '@shared/components'; import { ContributorsTableComponent } from '@shared/components/contributors'; diff --git a/src/app/features/metadata/dialogs/contributors-dialog/contributors-dialog.component.ts b/src/app/features/metadata/dialogs/contributors-dialog/contributors-dialog.component.ts index dd5693767..7f7d430e0 100644 --- a/src/app/features/metadata/dialogs/contributors-dialog/contributors-dialog.component.ts +++ b/src/app/features/metadata/dialogs/contributors-dialog/contributors-dialog.component.ts @@ -4,7 +4,6 @@ import { TranslatePipe } from '@ngx-translate/core'; import { Button } from 'primeng/button'; import { DynamicDialogConfig, DynamicDialogRef } from 'primeng/dynamicdialog'; -import { TablePageEvent } from 'primeng/table'; import { filter } from 'rxjs'; @@ -41,10 +40,11 @@ import { ContributorsSelectors, DeleteContributor, GetAllContributors, + LoadMoreContributors, UpdateBibliographyFilter, UpdateContributorsSearchValue, UpdatePermissionFilter, -} from '@osf/shared/stores'; +} from '@osf/shared/stores/contributors'; import { MetadataSelectors } from '../../store'; @@ -70,16 +70,18 @@ export class ContributorsDialogComponent implements OnInit { contributorsTotalCount = select(ContributorsSelectors.getContributorsTotalCount); hasAdminAccess = select(MetadataSelectors.hasAdminAccess); contributors = signal([]); - page = select(ContributorsSelectors.getContributorsPageNumber); + isLoadingMore = select(ContributorsSelectors.isContributorsLoadingMore); pageSize = select(ContributorsSelectors.getContributorsPageSize); + changesMade = signal(false); currentUser = select(UserSelectors.getCurrentUser); readonly tableParams = computed(() => ({ ...DEFAULT_TABLE_PARAMS, totalRecords: this.contributorsTotalCount(), - paginator: this.contributorsTotalCount() > DEFAULT_TABLE_PARAMS.rows, - firstRowIndex: (this.page() - 1) * this.pageSize(), + paginator: false, + scrollable: true, + firstRowIndex: 0, rows: this.pageSize(), })); @@ -92,6 +94,7 @@ export class ContributorsDialogComponent implements OnInit { addContributor: AddContributor, bulkAddContributors: BulkAddContributors, bulkUpdateContributors: BulkUpdateContributors, + loadMoreContributors: LoadMoreContributors, }); private readonly resourceType: ResourceType; @@ -117,6 +120,7 @@ export class ContributorsDialogComponent implements OnInit { } ngOnInit(): void { + this.actions.getContributors(this.resourceId, this.resourceType); this.setSearchSubscription(); } @@ -127,13 +131,10 @@ export class ContributorsDialogComponent implements OnInit { } openAddContributorDialog(): void { - const addedContributorIds = this.initialContributors().map((x) => x.userId); - this.customDialogService .open(AddContributorDialogComponent, { header: 'project.contributors.addDialog.addRegisteredContributor', width: '448px', - data: addedContributorIds, }) .onClose.pipe( filter((res: ContributorDialogAddModel) => !!res), @@ -147,9 +148,10 @@ export class ContributorsDialogComponent implements OnInit { this.actions .bulkAddContributors(this.resourceId, this.resourceType, res.data) .pipe(takeUntilDestroyed(this.destroyRef)) - .subscribe(() => - this.toastService.showSuccess('project.contributors.toastMessages.multipleAddSuccessMessage') - ); + .subscribe(() => { + this.changesMade.set(true); + this.toastService.showSuccess('project.contributors.toastMessages.multipleAddSuccessMessage'); + }); } } }); @@ -172,7 +174,10 @@ export class ContributorsDialogComponent implements OnInit { const params = { name: res.data[0].fullName }; this.actions.addContributor(this.resourceId, this.resourceType, res.data[0]).subscribe({ - next: () => this.toastService.showSuccess('project.contributors.toastMessages.addSuccessMessage', params), + next: () => { + this.changesMade.set(true); + this.toastService.showSuccess('project.contributors.toastMessages.addSuccessMessage', params); + }, }); } }); @@ -192,12 +197,13 @@ export class ContributorsDialogComponent implements OnInit { .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe({ next: () => { + this.changesMade.set(true); this.toastService.showSuccess('project.contributors.removeDialog.successMessage', { name: contributor.fullName, }); if (isDeletingSelf) { - this.dialogRef.close(); + this.dialogRef.close(this.changesMade()); this.router.navigate(['/']); } }, @@ -206,11 +212,8 @@ export class ContributorsDialogComponent implements OnInit { }); } - pageChanged(event: TablePageEvent) { - const page = Math.floor(event.first / event.rows) + 1; - const pageSize = event.rows; - - this.actions.getContributors(this.resourceId, this.resourceType, page, pageSize); + loadMoreContributors(): void { + this.actions.loadMoreContributors(this.resourceId, this.resourceType); } cancel() { @@ -218,7 +221,7 @@ export class ContributorsDialogComponent implements OnInit { } onClose(): void { - this.dialogRef.close(); + this.dialogRef.close(this.changesMade()); } onSave(): void { @@ -227,8 +230,9 @@ export class ContributorsDialogComponent implements OnInit { this.actions .bulkUpdateContributors(this.resourceId, this.resourceType, updatedContributors) .pipe(takeUntilDestroyed(this.destroyRef)) - .subscribe(() => - this.toastService.showSuccess('project.contributors.toastMessages.multipleUpdateSuccessMessage') - ); + .subscribe(() => { + this.changesMade.set(true); + this.toastService.showSuccess('project.contributors.toastMessages.multipleUpdateSuccessMessage'); + }); } } diff --git a/src/app/features/metadata/metadata.component.html b/src/app/features/metadata/metadata.component.html index a5adc3975..fe576760d 100644 --- a/src/app/features/metadata/metadata.component.html +++ b/src/app/features/metadata/metadata.component.html @@ -38,7 +38,10 @@ this.isMetadataLoading() || - this.isContributorsLoading() || this.areInstitutionsLoading() || this.isSubmitting() || this.areResourceInstitutionsSubmitting() @@ -320,23 +326,24 @@ export class MetadataComponent implements OnInit { this.actions.updateMetadata(this.resourceId, this.resourceType(), { tags }); } + handleLoadMoreContributors(): void { + this.actions.loadMoreBibliographicContributors(this.resourceId, this.resourceType()); + } + openEditContributorDialog(): void { this.customDialogService .open(ContributorsDialogComponent, { header: 'project.metadata.contributors.editContributors', - width: '600px', + width: '800px', data: { resourceId: this.resourceId, resourceType: this.resourceType(), }, }) - .onClose.subscribe((result) => { - if (result) { - this.actions.getResourceMetadata(this.resourceId, this.resourceType()); - this.toastService.showSuccess('project.metadata.contributors.updateSucceed'); + .onClose.subscribe((changesMade) => { + if (changesMade) { + this.actions.getContributors(this.resourceId, this.resourceType()); } - - this.actions.updateContributorsSearchValue(null); }); } diff --git a/src/app/features/moderation/components/collection-moderation-submissions/collection-moderation-submissions.component.spec.ts b/src/app/features/moderation/components/collection-moderation-submissions/collection-moderation-submissions.component.spec.ts index bd7f22322..88d55311b 100644 --- a/src/app/features/moderation/components/collection-moderation-submissions/collection-moderation-submissions.component.spec.ts +++ b/src/app/features/moderation/components/collection-moderation-submissions/collection-moderation-submissions.component.spec.ts @@ -4,7 +4,7 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; import { ActivatedRoute, Router } from '@angular/router'; import { CollectionSubmissionsListComponent } from '@osf/features/moderation/components'; -import { CollectionsSelectors } from '@osf/shared/stores'; +import { CollectionsSelectors } from '@osf/shared/stores/collections'; import { CustomPaginatorComponent, IconComponent, LoadingSpinnerComponent, SelectComponent } from '@shared/components'; import { SubmissionReviewStatus } from '../../enums'; diff --git a/src/app/features/moderation/components/collection-moderation-submissions/collection-moderation-submissions.component.ts b/src/app/features/moderation/components/collection-moderation-submissions/collection-moderation-submissions.component.ts index 867e22cda..16f0fb2e4 100644 --- a/src/app/features/moderation/components/collection-moderation-submissions/collection-moderation-submissions.component.ts +++ b/src/app/features/moderation/components/collection-moderation-submissions/collection-moderation-submissions.component.ts @@ -24,7 +24,7 @@ import { GetCollectionDetails, SearchCollectionSubmissions, SetPageNumber, -} from '@osf/shared/stores'; +} from '@osf/shared/stores/collections'; import { COLLECTIONS_SUBMISSIONS_REVIEW_OPTIONS } from '../../constants'; import { SubmissionReviewStatus } from '../../enums'; diff --git a/src/app/features/moderation/components/collection-submission-item/collection-submission-item.component.spec.ts b/src/app/features/moderation/components/collection-submission-item/collection-submission-item.component.spec.ts index 64180a945..6160eae16 100644 --- a/src/app/features/moderation/components/collection-submission-item/collection-submission-item.component.spec.ts +++ b/src/app/features/moderation/components/collection-submission-item/collection-submission-item.component.spec.ts @@ -4,7 +4,7 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; import { ActivatedRoute, Router } from '@angular/router'; import { CollectionSubmissionWithGuid } from '@osf/shared/models'; -import { CollectionsSelectors } from '@osf/shared/stores'; +import { CollectionsSelectors } from '@osf/shared/stores/collections'; import { IconComponent } from '@shared/components'; import { DateAgoPipe } from '@shared/pipes'; diff --git a/src/app/features/moderation/components/collection-submission-item/collection-submission-item.component.ts b/src/app/features/moderation/components/collection-submission-item/collection-submission-item.component.ts index 01e5e221b..cea58dfca 100644 --- a/src/app/features/moderation/components/collection-submission-item/collection-submission-item.component.ts +++ b/src/app/features/moderation/components/collection-submission-item/collection-submission-item.component.ts @@ -11,7 +11,7 @@ import { collectionFilterNames } from '@osf/features/collections/constants'; import { IconComponent, TruncatedTextComponent } from '@osf/shared/components'; import { CollectionSubmissionWithGuid } from '@osf/shared/models'; import { DateAgoPipe } from '@osf/shared/pipes'; -import { CollectionsSelectors } from '@osf/shared/stores'; +import { CollectionsSelectors } from '@osf/shared/stores/collections'; import { ReviewStatusIcon } from '../../constants'; import { SubmissionReviewStatus } from '../../enums'; diff --git a/src/app/features/moderation/components/moderators-list/moderators-list.component.html b/src/app/features/moderation/components/moderators-list/moderators-list.component.html index b221012f6..65e154244 100644 --- a/src/app/features/moderation/components/moderators-list/moderators-list.component.html +++ b/src/app/features/moderation/components/moderators-list/moderators-list.component.html @@ -1,7 +1,7 @@
- @if (isCurrentUserAdminModerator()) { + @if (hasAdminAccess()) {
{ signals: [ { selector: UserSelectors.getCurrentUser, value: mockCurrentUser }, { selector: ModeratorsSelectors.getModerators, value: mockModerators }, + { selector: ProviderSelectors.hasAdminAccess, value: false }, { selector: ModeratorsSelectors.isModeratorsLoading, value: false }, ], }), @@ -110,7 +112,7 @@ describe('ModeratorsListComponent', () => { fixture.detectChanges(); - expect(component.isCurrentUserAdminModerator()).toBe(false); + expect(component.hasAdminAccess()).toBe(false); }); it('should return false for admin moderator when user is not found', () => { @@ -121,7 +123,7 @@ describe('ModeratorsListComponent', () => { fixture.detectChanges(); - expect(component.isCurrentUserAdminModerator()).toBe(false); + expect(component.hasAdminAccess()).toBe(false); }); it('should load moderators on initialization', () => { diff --git a/src/app/features/moderation/components/moderators-list/moderators-list.component.ts b/src/app/features/moderation/components/moderators-list/moderators-list.component.ts index cb2b979ff..e7af36f1e 100644 --- a/src/app/features/moderation/components/moderators-list/moderators-list.component.ts +++ b/src/app/features/moderation/components/moderators-list/moderators-list.component.ts @@ -22,6 +22,7 @@ import { takeUntilDestroyed, toSignal } from '@angular/core/rxjs-interop'; import { FormControl } from '@angular/forms'; import { ActivatedRoute } from '@angular/router'; +import { ProviderSelectors } from '@core/store/provider'; import { UserSelectors } from '@core/store/user'; import { SearchInputComponent } from '@osf/shared/components'; import { DEFAULT_TABLE_PARAMS } from '@osf/shared/constants'; @@ -29,7 +30,7 @@ import { ResourceType } from '@osf/shared/enums'; import { TableParameters } from '@osf/shared/models'; import { CustomConfirmationService, CustomDialogService, ToastService } from '@osf/shared/services'; -import { AddModeratorType, ModeratorPermission } from '../../enums'; +import { AddModeratorType } from '../../enums'; import { ModeratorDialogAddModel, ModeratorModel } from '../../models'; import { AddModerator, @@ -70,6 +71,7 @@ export class ModeratorsListComponent implements OnInit { initialModerators = select(ModeratorsSelectors.getModerators); isModeratorsLoading = select(ModeratorsSelectors.isModeratorsLoading); moderatorsTotalCount = select(ModeratorsSelectors.getModeratorsTotalCount); + hasAdminAccess = select(ProviderSelectors.hasAdminAccess); currentUser = select(UserSelectors.getCurrentUser); readonly tableParams = computed(() => ({ @@ -78,17 +80,6 @@ export class ModeratorsListComponent implements OnInit { paginator: this.moderatorsTotalCount() > DEFAULT_TABLE_PARAMS.rows, })); - isCurrentUserAdminModerator = computed(() => { - const currentUserId = this.currentUser()?.id; - const initialModerators = this.initialModerators(); - if (!currentUserId) return false; - - return initialModerators.some( - (moderator: ModeratorModel) => - moderator.userId === currentUserId && moderator.permission === ModeratorPermission.Admin - ); - }); - actions = createDispatchMap({ loadModerators: LoadModerators, updateSearchValue: UpdateModeratorsSearchValue, diff --git a/src/app/features/moderation/components/moderators-table/moderators-table.component.html b/src/app/features/moderation/components/moderators-table/moderators-table.component.html index 15588e823..03af03e71 100644 --- a/src/app/features/moderation/components/moderators-table/moderators-table.component.html +++ b/src/app/features/moderation/components/moderators-table/moderators-table.component.html @@ -37,7 +37,7 @@
- @if (isCurrentUserAdminModerator()) { + @if (hasAdminAccess()) { - @if (isCurrentUserAdminModerator() || currentUserId() === item.id) { + @if (hasAdminAccess() || currentUserId() === item.id) { } diff --git a/src/app/features/moderation/components/moderators-table/moderators-table.component.spec.ts b/src/app/features/moderation/components/moderators-table/moderators-table.component.spec.ts index 312360ba1..0df3b64a4 100644 --- a/src/app/features/moderation/components/moderators-table/moderators-table.component.spec.ts +++ b/src/app/features/moderation/components/moderators-table/moderators-table.component.spec.ts @@ -44,7 +44,7 @@ describe('ModeratorsTableComponent', () => { fixture.componentRef.setInput('tableParams', mockTableParams); fixture.componentRef.setInput('currentUserId', 'test-user-id'); - fixture.componentRef.setInput('isCurrentUserAdminModerator', false); + fixture.componentRef.setInput('hasAdminAccess', false); }); it('should create', () => { @@ -56,7 +56,7 @@ describe('ModeratorsTableComponent', () => { fixture.componentRef.setInput('items', mockModerators); fixture.componentRef.setInput('isLoading', true); fixture.componentRef.setInput('currentUserId', 'current-user-123'); - fixture.componentRef.setInput('isCurrentUserAdminModerator', true); + fixture.componentRef.setInput('hasAdminAccess', true); fixture.componentRef.setInput('tableParams', mockTableParams); fixture.detectChanges(); @@ -64,7 +64,7 @@ describe('ModeratorsTableComponent', () => { expect(component.items()).toEqual(mockModerators); expect(component.isLoading()).toBe(true); expect(component.currentUserId()).toBe('current-user-123'); - expect(component.isCurrentUserAdminModerator()).toBe(true); + expect(component.hasAdminAccess()).toBe(true); expect(component.tableParams()).toEqual(mockTableParams); }); @@ -118,7 +118,7 @@ describe('ModeratorsTableComponent', () => { fixture.componentRef.setInput('items', []); fixture.componentRef.setInput('tableParams', mockTableParams); fixture.componentRef.setInput('currentUserId', 'test-user-id'); - fixture.componentRef.setInput('isCurrentUserAdminModerator', false); + fixture.componentRef.setInput('hasAdminAccess', false); fixture.detectChanges(); @@ -128,7 +128,7 @@ describe('ModeratorsTableComponent', () => { it('should handle undefined currentUserId', () => { fixture.componentRef.setInput('currentUserId', undefined); fixture.componentRef.setInput('tableParams', mockTableParams); - fixture.componentRef.setInput('isCurrentUserAdminModerator', false); + fixture.componentRef.setInput('hasAdminAccess', false); fixture.detectChanges(); diff --git a/src/app/features/moderation/components/moderators-table/moderators-table.component.ts b/src/app/features/moderation/components/moderators-table/moderators-table.component.ts index 058bb7977..80e61d8d7 100644 --- a/src/app/features/moderation/components/moderators-table/moderators-table.component.ts +++ b/src/app/features/moderation/components/moderators-table/moderators-table.component.ts @@ -31,7 +31,7 @@ export class ModeratorsTableComponent { isLoading = input(false); tableParams = input.required(); currentUserId = input.required(); - isCurrentUserAdminModerator = input.required(); + hasAdminAccess = input.required(); update = output(); remove = output(); diff --git a/src/app/features/moderation/components/preprint-submission-item/preprint-submission-item.component.html b/src/app/features/moderation/components/preprint-submission-item/preprint-submission-item.component.html index ae7decd48..8de29f158 100644 --- a/src/app/features/moderation/components/preprint-submission-item/preprint-submission-item.component.html +++ b/src/app/features/moderation/components/preprint-submission-item/preprint-submission-item.component.html @@ -39,7 +39,7 @@ -
+

{{ 'common.labels.contributors' | translate }}:

@if (submission().contributorsLoading) { diff --git a/src/app/features/moderation/mappers/preprint-moderation.mapper.ts b/src/app/features/moderation/mappers/preprint-moderation.mapper.ts index 0912104be..cf4500f0b 100644 --- a/src/app/features/moderation/mappers/preprint-moderation.mapper.ts +++ b/src/app/features/moderation/mappers/preprint-moderation.mapper.ts @@ -50,6 +50,7 @@ export class PreprintModerationMapper { return { id: response.id, name: response.attributes.name, + permissions: response.attributes.permissions, reviewsCommentsAnonymous: response.attributes.reviews_comments_anonymous, reviewsCommentsPrivate: response.attributes.reviews_comments_private, reviewsWorkflow: response.attributes.reviews_workflow, diff --git a/src/app/features/moderation/models/preprint-provider-moderation-info.model.ts b/src/app/features/moderation/models/preprint-provider-moderation-info.model.ts index af069ba44..73649885b 100644 --- a/src/app/features/moderation/models/preprint-provider-moderation-info.model.ts +++ b/src/app/features/moderation/models/preprint-provider-moderation-info.model.ts @@ -1,9 +1,12 @@ +import { ReviewPermissions } from '@osf/shared/enums'; + export interface PreprintProviderModerationInfo { id: string; name: string; - submissionCount?: number; + permissions: ReviewPermissions[]; reviewsCommentsAnonymous: boolean; reviewsCommentsPrivate: boolean; reviewsWorkflow: string; + submissionCount?: number; supportEmail: string | null; } diff --git a/src/app/features/moderation/pages/collection-moderation/collection-moderation.component.ts b/src/app/features/moderation/pages/collection-moderation/collection-moderation.component.ts index a6ea58f3d..df433f843 100644 --- a/src/app/features/moderation/pages/collection-moderation/collection-moderation.component.ts +++ b/src/app/features/moderation/pages/collection-moderation/collection-moderation.component.ts @@ -12,7 +12,7 @@ import { ActivatedRoute, Router, RouterOutlet } from '@angular/router'; import { ClearCurrentProvider } from '@core/store/provider'; import { SelectComponent, SubHeaderComponent } from '@osf/shared/components'; import { IS_MEDIUM, Primitive } from '@osf/shared/helpers'; -import { GetCollectionProvider } from '@osf/shared/stores'; +import { GetCollectionProvider } from '@osf/shared/stores/collections'; import { COLLECTION_MODERATION_TABS } from '../../constants'; import { CollectionModerationTab } from '../../enums'; diff --git a/src/app/features/moderation/pages/preprint-moderation/preprint-moderation.component.ts b/src/app/features/moderation/pages/preprint-moderation/preprint-moderation.component.ts index 5a07a1385..90d1538d1 100644 --- a/src/app/features/moderation/pages/preprint-moderation/preprint-moderation.component.ts +++ b/src/app/features/moderation/pages/preprint-moderation/preprint-moderation.component.ts @@ -1,3 +1,5 @@ +import { createDispatchMap } from '@ngxs/store'; + import { TranslatePipe } from '@ngx-translate/core'; import { Tab, TabList, TabPanels, Tabs } from 'primeng/tabs'; @@ -13,6 +15,7 @@ import { IS_MEDIUM, Primitive } from '@osf/shared/helpers'; import { PREPRINT_MODERATION_TABS } from '../../constants'; import { PreprintModerationTab } from '../../enums'; +import { GetPreprintProvider } from '../../store/preprint-moderation'; @Component({ selector: 'osf-preprint-moderation', @@ -41,8 +44,19 @@ export class PreprintModerationComponent implements OnInit { selectedTab = PreprintModerationTab.Submissions; + private readonly actions = createDispatchMap({ getPreprintProvider: GetPreprintProvider }); + ngOnInit(): void { this.selectedTab = this.route.snapshot.firstChild?.data['tab'] as PreprintModerationTab; + + const id = this.route.snapshot.params['providerId']; + + if (!id) { + this.router.navigate(['/not-found']); + return; + } + + this.actions.getPreprintProvider(id); } onTabChange(value: Primitive): void { diff --git a/src/app/features/moderation/services/preprint-moderation.service.ts b/src/app/features/moderation/services/preprint-moderation.service.ts index c33a8dd9b..0696c5f2c 100644 --- a/src/app/features/moderation/services/preprint-moderation.service.ts +++ b/src/app/features/moderation/services/preprint-moderation.service.ts @@ -3,7 +3,7 @@ import { catchError, forkJoin, map, Observable, of, switchMap } from 'rxjs'; import { inject, Injectable } from '@angular/core'; import { ENVIRONMENT } from '@core/provider/environment.provider'; -import { JsonApiResponse, PaginatedData, ResponseJsonApi } from '@osf/shared/models'; +import { PaginatedData, ResponseJsonApi } from '@osf/shared/models'; import { JsonApiService } from '@osf/shared/services'; import { PreprintSubmissionsSort } from '../enums'; @@ -36,7 +36,7 @@ export class PreprintModerationService { const baseUrl = `${this.apiUrl}/providers/preprints/?filter[permissions]=view_actions,set_up_moderation`; return this.jsonApiService - .get>(baseUrl) + .get>(baseUrl) .pipe(map((response) => response.data.map((x) => PreprintModerationMapper.fromPreprintRelatedCounts(x)))); } @@ -44,7 +44,7 @@ export class PreprintModerationService { const baseUrl = `${this.apiUrl}/providers/preprints/${id}/?related_counts=true`; return this.jsonApiService - .get>(baseUrl) + .get>(baseUrl) .pipe(map((response) => PreprintModerationMapper.fromPreprintRelatedCounts(response.data))); } diff --git a/src/app/features/moderation/store/moderators/moderators.state.ts b/src/app/features/moderation/store/moderators/moderators.state.ts index 866b7d17d..51138ccfb 100644 --- a/src/app/features/moderation/store/moderators/moderators.state.ts +++ b/src/app/features/moderation/store/moderators/moderators.state.ts @@ -88,7 +88,7 @@ export class ModeratorsState { } @Action(UpdateModerator) - updateCollectionModerator(ctx: StateContext, action: UpdateModerator) { + updateModerator(ctx: StateContext, action: UpdateModerator) { const state = ctx.getState(); if (!action.resourceType) { @@ -108,7 +108,7 @@ export class ModeratorsState { } @Action(DeleteModerator) - deleteCollectionModerator(ctx: StateContext, action: DeleteModerator) { + deleteModerator(ctx: StateContext, action: DeleteModerator) { const state = ctx.getState(); if (!action.resourceType) { diff --git a/src/app/features/moderation/store/preprint-moderation/preprint-moderation.state.ts b/src/app/features/moderation/store/preprint-moderation/preprint-moderation.state.ts index a8189c0fc..bae189e49 100644 --- a/src/app/features/moderation/store/preprint-moderation/preprint-moderation.state.ts +++ b/src/app/features/moderation/store/preprint-moderation/preprint-moderation.state.ts @@ -5,8 +5,9 @@ import { catchError, forkJoin, map, switchMap, tap } from 'rxjs'; import { inject, Injectable } from '@angular/core'; +import { SetCurrentProvider } from '@core/store/provider'; import { DEFAULT_TABLE_PARAMS } from '@osf/shared/constants'; -import { ResourceType } from '@osf/shared/enums'; +import { CurrentResourceType, ResourceType } from '@osf/shared/enums'; import { handleSectionError } from '@osf/shared/helpers'; import { ContributorsService } from '@osf/shared/services'; @@ -92,6 +93,16 @@ export class PreprintModerationState { tap((data) => { const exists = ctx.getState().preprintProviders.data.some((p) => p.id === data.id); + ctx.dispatch( + new SetCurrentProvider({ + id: data.id, + name: data.name, + type: CurrentResourceType.Preprints, + permissions: data.permissions, + reviewsWorkflow: data.reviewsWorkflow, + }) + ); + ctx.setState( patch({ preprintProviders: patch({ diff --git a/src/app/features/my-projects/components/create-project-dialog/create-project-dialog.component.spec.ts b/src/app/features/my-projects/components/create-project-dialog/create-project-dialog.component.spec.ts index 65f89d8c6..f3743edb5 100644 --- a/src/app/features/my-projects/components/create-project-dialog/create-project-dialog.component.spec.ts +++ b/src/app/features/my-projects/components/create-project-dialog/create-project-dialog.component.spec.ts @@ -10,7 +10,7 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; import { DEFAULT_TABLE_PARAMS } from '@osf/shared/constants'; import { ProjectFormControls } from '@osf/shared/enums'; -import { CreateProject, GetMyProjects, MyResourcesSelectors } from '@osf/shared/stores'; +import { CreateProject, GetMyProjects, MyResourcesSelectors } from '@osf/shared/stores/my-resources'; import { AddProjectFormComponent } from '@shared/components'; import { CreateProjectDialogComponent } from './create-project-dialog.component'; diff --git a/src/app/features/my-projects/components/create-project-dialog/create-project-dialog.component.ts b/src/app/features/my-projects/components/create-project-dialog/create-project-dialog.component.ts index 6f74d333a..90eacddad 100644 --- a/src/app/features/my-projects/components/create-project-dialog/create-project-dialog.component.ts +++ b/src/app/features/my-projects/components/create-project-dialog/create-project-dialog.component.ts @@ -13,7 +13,7 @@ import { DEFAULT_TABLE_PARAMS } from '@osf/shared/constants'; import { ProjectFormControls } from '@osf/shared/enums'; import { CustomValidators } from '@osf/shared/helpers'; import { ProjectForm } from '@osf/shared/models'; -import { CreateProject, GetMyProjects, MyResourcesSelectors } from '@osf/shared/stores'; +import { CreateProject, GetMyProjects, MyResourcesSelectors } from '@osf/shared/stores/my-resources'; @Component({ selector: 'osf-create-project-dialog', diff --git a/src/app/features/my-projects/my-projects.component.spec.ts b/src/app/features/my-projects/my-projects.component.spec.ts index e8b958e17..a5868a1d5 100644 --- a/src/app/features/my-projects/my-projects.component.spec.ts +++ b/src/app/features/my-projects/my-projects.component.spec.ts @@ -8,7 +8,8 @@ import { ActivatedRoute, Router } from '@angular/router'; import { MyProjectsTab } from '@osf/features/my-projects/enums'; import { SortOrder } from '@osf/shared/enums'; import { IS_MEDIUM } from '@osf/shared/helpers'; -import { BookmarksSelectors, MyResourcesSelectors } from '@osf/shared/stores'; +import { BookmarksSelectors } from '@osf/shared/stores/bookmarks'; +import { MyResourcesSelectors } from '@osf/shared/stores/my-resources'; import { MyProjectsTableComponent, SearchInputComponent, diff --git a/src/app/features/my-projects/my-projects.component.ts b/src/app/features/my-projects/my-projects.component.ts index b9381dc6a..618c2df18 100644 --- a/src/app/features/my-projects/my-projects.component.ts +++ b/src/app/features/my-projects/my-projects.component.ts @@ -33,16 +33,15 @@ import { DEFAULT_TABLE_PARAMS } from '@osf/shared/constants'; import { ResourceType, SortOrder } from '@osf/shared/enums'; import { IS_MEDIUM } from '@osf/shared/helpers'; import { MyResourcesItem, MyResourcesSearchFilters, QueryParams, TableParameters } from '@osf/shared/models'; +import { BookmarksSelectors, GetBookmarksCollectionId } from '@osf/shared/stores/bookmarks'; import { - BookmarksSelectors, ClearMyResources, - GetBookmarksCollectionId, GetMyBookmarks, GetMyPreprints, GetMyProjects, GetMyRegistrations, MyResourcesSelectors, -} from '@osf/shared/stores'; +} from '@osf/shared/stores/my-resources'; import { CustomDialogService, ProjectRedirectDialogService } from '@shared/services'; import { PROJECT_FILTER_OPTIONS } from './constants/project-filter-options.const'; diff --git a/src/app/features/preprints/components/preprint-details/additional-info/additional-info.component.html b/src/app/features/preprints/components/preprint-details/additional-info/additional-info.component.html index c5c929c7e..18a160459 100644 --- a/src/app/features/preprints/components/preprint-details/additional-info/additional-info.component.html +++ b/src/app/features/preprints/components/preprint-details/additional-info/additional-info.component.html @@ -30,18 +30,9 @@

{{ 'preprints.details.publicationDoi' | translate }}

}
-

{{ 'preprints.preprintStepper.review.sections.metadata.license' | translate }}

+

{{ 'common.labels.license' | translate }}

- - - -

{{ license()!.name }}

-
- -

{{ license()!.text | interpolate: licenseOptionsRecord() }}

-
-
-
+
@@ -53,7 +44,7 @@

{{ 'preprints.preprintStepper.review.sections.metadata.subjects' | translate } @if (areSelectedSubjectsLoading()) { - + }

@@ -83,8 +74,8 @@

{{ 'preprints.preprintStepper.review.sections.metadata.tags' | translate }}<
@for (i of skeletonData; track $index) {
- - + +
}
diff --git a/src/app/features/preprints/components/preprint-details/additional-info/additional-info.component.spec.ts b/src/app/features/preprints/components/preprint-details/additional-info/additional-info.component.spec.ts index 5461b28aa..d49611b63 100644 --- a/src/app/features/preprints/components/preprint-details/additional-info/additional-info.component.spec.ts +++ b/src/app/features/preprints/components/preprint-details/additional-info/additional-info.component.spec.ts @@ -1,11 +1,13 @@ import { MockComponent, MockPipe } from 'ng-mocks'; import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { Router } from '@angular/router'; -import { CitationSectionComponent } from '@osf/features/preprints/components/preprint-details/citation-section/citation-section.component'; import { PreprintSelectors } from '@osf/features/preprints/store/preprint'; -import { InterpolatePipe } from '@shared/pipes'; -import { SubjectsSelectors } from '@shared/stores'; +import { InterpolatePipe } from '@osf/shared/pipes'; +import { SubjectsSelectors } from '@osf/shared/stores/subjects'; + +import { CitationSectionComponent } from '../citation-section/citation-section.component'; import { AdditionalInfoComponent } from './additional-info.component'; @@ -76,13 +78,14 @@ describe('AdditionalInfoComponent', () => { expect(component.skeletonData.every((item) => item === null)).toBe(true); }); - it('should return license from preprint when available', () => { - const license = component.license(); - expect(license).toBe(mockPreprint.embeddedLicense); - }); + it('should navigate to search page with tag when tagClicked is called', () => { + const router = TestBed.inject(Router); + const navigateSpy = jest.spyOn(router, 'navigate'); - it('should return license options record from preprint when available', () => { - const licenseOptionsRecord = component.licenseOptionsRecord(); - expect(licenseOptionsRecord).toEqual(mockPreprint.licenseOptions); + component.tagClicked('test-tag'); + + expect(navigateSpy).toHaveBeenCalledWith(['/search'], { + queryParams: { search: 'test-tag' }, + }); }); }); diff --git a/src/app/features/preprints/components/preprint-details/additional-info/additional-info.component.ts b/src/app/features/preprints/components/preprint-details/additional-info/additional-info.component.ts index c18ba618d..894b8c32b 100644 --- a/src/app/features/preprints/components/preprint-details/additional-info/additional-info.component.ts +++ b/src/app/features/preprints/components/preprint-details/additional-info/additional-info.component.ts @@ -2,7 +2,6 @@ import { createDispatchMap, select } from '@ngxs/store'; import { TranslatePipe } from '@ngx-translate/core'; -import { Accordion, AccordionContent, AccordionHeader, AccordionPanel } from 'primeng/accordion'; import { Card } from 'primeng/card'; import { Skeleton } from 'primeng/skeleton'; import { Tag } from 'primeng/tag'; @@ -11,27 +10,16 @@ import { DatePipe } from '@angular/common'; import { ChangeDetectionStrategy, Component, computed, effect, inject, input } from '@angular/core'; import { Router } from '@angular/router'; -import { CitationSectionComponent } from '@osf/features/preprints/components/preprint-details/citation-section/citation-section.component'; import { PreprintSelectors } from '@osf/features/preprints/store/preprint'; +import { LicenseDisplayComponent } from '@osf/shared/components'; import { ResourceType } from '@shared/enums'; -import { InterpolatePipe } from '@shared/pipes'; -import { FetchSelectedSubjects, SubjectsSelectors } from '@shared/stores'; +import { FetchSelectedSubjects, SubjectsSelectors } from '@shared/stores/subjects'; + +import { CitationSectionComponent } from '../citation-section/citation-section.component'; @Component({ selector: 'osf-preprint-additional-info', - imports: [ - Card, - TranslatePipe, - Tag, - Skeleton, - DatePipe, - Accordion, - AccordionPanel, - AccordionHeader, - AccordionContent, - InterpolatePipe, - CitationSectionComponent, - ], + imports: [Card, TranslatePipe, Tag, Skeleton, DatePipe, CitationSectionComponent, LicenseDisplayComponent], templateUrl: './additional-info.component.html', styleUrl: './additional-info.component.scss', changeDetection: ChangeDetectionStrategy.OnPush, diff --git a/src/app/features/preprints/components/preprint-details/citation-section/citation-section.component.spec.ts b/src/app/features/preprints/components/preprint-details/citation-section/citation-section.component.spec.ts index 599b465d6..a6512b47c 100644 --- a/src/app/features/preprints/components/preprint-details/citation-section/citation-section.component.spec.ts +++ b/src/app/features/preprints/components/preprint-details/citation-section/citation-section.component.spec.ts @@ -1,7 +1,7 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; import { CitationStyle } from '@shared/models'; -import { CitationsSelectors } from '@shared/stores'; +import { CitationsSelectors } from '@shared/stores/citations'; import { CitationSectionComponent } from './citation-section.component'; diff --git a/src/app/features/preprints/components/preprint-details/citation-section/citation-section.component.ts b/src/app/features/preprints/components/preprint-details/citation-section/citation-section.component.ts index 81edf77a1..7e595f6ab 100644 --- a/src/app/features/preprints/components/preprint-details/citation-section/citation-section.component.ts +++ b/src/app/features/preprints/components/preprint-details/citation-section/citation-section.component.ts @@ -30,7 +30,7 @@ import { GetCitationStyles, GetStyledCitation, UpdateCustomCitation, -} from '@shared/stores'; +} from '@shared/stores/citations'; @Component({ selector: 'osf-preprint-citation-section', diff --git a/src/app/features/preprints/components/preprint-details/general-information/general-information.component.html b/src/app/features/preprints/components/preprint-details/general-information/general-information.component.html index 1864e4d31..a20d43ff2 100644 --- a/src/app/features/preprints/components/preprint-details/general-information/general-information.component.html +++ b/src/app/features/preprints/components/preprint-details/general-information/general-information.component.html @@ -7,11 +7,12 @@

{{ 'preprints.preprintStepper.review.sections.metadata.authors' | translate }}

- - - @if (areContributorsLoading()) { - - } +
@@ -118,8 +119,8 @@

{{ 'preprints.preprintStepper.review.sections.authorAssertions.conflictOfInt
@for (i of skeletonData; track $index) {
- - + +
}
diff --git a/src/app/features/preprints/components/preprint-details/general-information/general-information.component.spec.ts b/src/app/features/preprints/components/preprint-details/general-information/general-information.component.spec.ts index e1e4de9c4..9baa56810 100644 --- a/src/app/features/preprints/components/preprint-details/general-information/general-information.component.spec.ts +++ b/src/app/features/preprints/components/preprint-details/general-information/general-information.component.spec.ts @@ -11,7 +11,8 @@ import { IconComponent, TruncatedTextComponent, } from '@shared/components'; -import { ContributorsSelectors, InstitutionsSelectors } from '@shared/stores'; +import { ContributorsSelectors } from '@shared/stores/contributors'; +import { InstitutionsSelectors } from '@shared/stores/institutions'; import { PreprintDoiSectionComponent } from '../preprint-doi-section/preprint-doi-section.component'; @@ -47,9 +48,7 @@ describe('GeneralInformationComponent', () => { ), ], providers: [ - MockProvider(ENVIRONMENT, { - webUrl: mockWebUrl, - }), + MockProvider(ENVIRONMENT, { webUrl: mockWebUrl }), provideMockStore({ signals: [ { @@ -61,11 +60,15 @@ describe('GeneralInformationComponent', () => { value: false, }, { - selector: ContributorsSelectors.getContributors, + selector: ContributorsSelectors.getBibliographicContributors, value: mockContributors, }, { - selector: ContributorsSelectors.isContributorsLoading, + selector: ContributorsSelectors.isBibliographicContributorsLoading, + value: false, + }, + { + selector: ContributorsSelectors.hasMoreBibliographicContributors, value: false, }, { @@ -83,26 +86,16 @@ describe('GeneralInformationComponent', () => { fixture.componentRef.setInput('preprintProvider', mockPreprintProvider); }); - it('should create', () => { - expect(component).toBeTruthy(); - }); - it('should return preprint from store', () => { const preprint = component.preprint(); expect(preprint).toBe(mockPreprint); }); it('should return contributors from store', () => { - const contributors = component.contributors(); + const contributors = component.bibliographicContributors(); expect(contributors).toBe(mockContributors); }); - it('should filter bibliographic contributors', () => { - const bibliographicContributors = component.bibliographicContributors(); - expect(bibliographicContributors).toHaveLength(1); - expect(bibliographicContributors.every((contributor) => contributor.isBibliographic)).toBe(true); - }); - it('should return affiliated institutions from store', () => { const institutions = component.affiliatedInstitutions(); expect(institutions).toBe(mockInstitutions); diff --git a/src/app/features/preprints/components/preprint-details/general-information/general-information.component.ts b/src/app/features/preprints/components/preprint-details/general-information/general-information.component.ts index c3cbfac6b..4f9b1650f 100644 --- a/src/app/features/preprints/components/preprint-details/general-information/general-information.component.ts +++ b/src/app/features/preprints/components/preprint-details/general-information/general-information.component.ts @@ -21,11 +21,11 @@ import { import { ResourceType } from '@osf/shared/enums'; import { ContributorsSelectors, - FetchResourceInstitutions, - GetAllContributors, - InstitutionsSelectors, + GetBibliographicContributors, + LoadMoreBibliographicContributors, ResetContributorsState, -} from '@osf/shared/stores'; +} from '@osf/shared/stores/contributors'; +import { FetchResourceInstitutions, InstitutionsSelectors } from '@osf/shared/stores/institutions'; import { PreprintDoiSectionComponent } from '../preprint-doi-section/preprint-doi-section.component'; @@ -53,10 +53,11 @@ export class GeneralInformationComponent implements OnDestroy { readonly PreregLinkInfo = PreregLinkInfo; private actions = createDispatchMap({ - getContributors: GetAllContributors, + getBibliographicContributors: GetBibliographicContributors, resetContributorsState: ResetContributorsState, fetchPreprintById: FetchPreprintById, fetchResourceInstitutions: FetchResourceInstitutions, + loadMoreBibliographicContributors: LoadMoreBibliographicContributors, }); preprintProvider = input.required(); @@ -67,9 +68,9 @@ export class GeneralInformationComponent implements OnDestroy { affiliatedInstitutions = select(InstitutionsSelectors.getResourceInstitutions); - contributors = select(ContributorsSelectors.getContributors); - areContributorsLoading = select(ContributorsSelectors.isContributorsLoading); - bibliographicContributors = computed(() => this.contributors().filter((contributor) => contributor.isBibliographic)); + bibliographicContributors = select(ContributorsSelectors.getBibliographicContributors); + areContributorsLoading = select(ContributorsSelectors.isBibliographicContributorsLoading); + hasMoreBibliographicContributors = select(ContributorsSelectors.hasMoreBibliographicContributors); skeletonData = Array.from({ length: 5 }, () => null); @@ -80,7 +81,7 @@ export class GeneralInformationComponent implements OnDestroy { const preprint = this.preprint(); if (!preprint) return; - this.actions.getContributors(this.preprint()!.id, ResourceType.Preprint); + this.actions.getBibliographicContributors(this.preprint()!.id, ResourceType.Preprint); this.actions.fetchResourceInstitutions(this.preprint()!.id, ResourceType.Preprint); }); } @@ -88,4 +89,8 @@ export class GeneralInformationComponent implements OnDestroy { ngOnDestroy(): void { this.actions.resetContributorsState(); } + + handleLoadMoreContributors(): void { + this.actions.loadMoreBibliographicContributors(this.preprint()?.id, ResourceType.Preprint); + } } diff --git a/src/app/features/preprints/components/preprint-details/preprint-doi-section/preprint-doi-section.component.html b/src/app/features/preprints/components/preprint-details/preprint-doi-section/preprint-doi-section.component.html index cceb3171b..b32a5e842 100644 --- a/src/app/features/preprints/components/preprint-details/preprint-doi-section/preprint-doi-section.component.html +++ b/src/app/features/preprints/components/preprint-details/preprint-doi-section/preprint-doi-section.component.html @@ -1,31 +1,32 @@ -@let preprintValue = preprint()!; +@let preprintValue = preprint(); @let preprintProviderValue = preprintProvider()!; +
-

{{ 'preprints.details.doi.title' | translate: { documentType: preprintProviderValue.preprintWord } }}

+

{{ 'preprints.details.doi.title' | translate: { documentType: preprintProviderValue?.preprintWord } }}

- @if (preprintValue.preprintDoiLink) { - @if (preprintValue.preprintDoiCreated) { - - {{ preprintValue.preprintDoiLink }} + @if (preprintValue?.preprintDoiLink) { + @if (preprintValue?.preprintDoiCreated) { + + {{ preprintValue?.preprintDoiLink }} } @else { -

{{ preprintValue.preprintDoiLink }}

+

{{ preprintValue?.preprintDoiLink }}

{{ 'preprints.details.doi.pendingDoiMinted' | translate }}

} } @else { - @if (!preprintValue.isPublic) { + @if (!preprintValue?.isPublic) {

{{ 'preprints.details.doi.pendingDoi' | translate: { documentType: preprintProviderValue.preprintWord } }}

- } @else if (preprintProvider()?.reviewsWorkflow && !preprintValue.isPublished) { + } @else if (preprintProvider()?.reviewsWorkflow && !preprintValue?.isPublished) {

{{ 'preprints.details.doi.pendingDoiModeration' | translate }}

} @else {

{{ 'preprints.details.doi.noDoi' | translate }}

diff --git a/src/app/features/preprints/components/preprint-details/preprint-doi-section/preprint-doi-section.component.spec.ts b/src/app/features/preprints/components/preprint-details/preprint-doi-section/preprint-doi-section.component.spec.ts index 67b5d5463..e4a95c947 100644 --- a/src/app/features/preprints/components/preprint-details/preprint-doi-section/preprint-doi-section.component.spec.ts +++ b/src/app/features/preprints/components/preprint-details/preprint-doi-section/preprint-doi-section.component.spec.ts @@ -1,6 +1,6 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; -import { Preprint, PreprintProviderDetails } from '@osf/features/preprints/models'; +import { PreprintModel, PreprintProviderDetails } from '@osf/features/preprints/models'; import { PreprintSelectors } from '@osf/features/preprints/store/preprint'; import { PreprintDoiSectionComponent } from './preprint-doi-section.component'; @@ -15,7 +15,7 @@ describe('PreprintDoiSectionComponent', () => { let component: PreprintDoiSectionComponent; let fixture: ComponentFixture; - const mockPreprint: Preprint = PREPRINT_MOCK; + const mockPreprint: PreprintModel = PREPRINT_MOCK; const mockProvider: PreprintProviderDetails = PREPRINT_PROVIDER_DETAILS_MOCK; const mockVersionIds = ['version-1', 'version-2', 'version-3']; @@ -50,10 +50,6 @@ describe('PreprintDoiSectionComponent', () => { fixture.componentRef.setInput('preprintProvider', mockProvider); }); - it('should create', () => { - expect(component).toBeTruthy(); - }); - it('should compute versions dropdown options from version IDs', () => { const options = component.versionsDropdownOptions(); expect(options).toEqual([ diff --git a/src/app/features/preprints/components/preprint-details/preprint-file-section/preprint-file-section.component.html b/src/app/features/preprints/components/preprint-details/preprint-file-section/preprint-file-section.component.html index 121fc728a..3e17a370b 100644 --- a/src/app/features/preprints/components/preprint-details/preprint-file-section/preprint-file-section.component.html +++ b/src/app/features/preprints/components/preprint-details/preprint-file-section/preprint-file-section.component.html @@ -1,13 +1,14 @@
@if (safeLink()) { } @if (isIframeLoading || isFileLoading()) { diff --git a/src/app/features/preprints/components/preprint-details/preprint-file-section/preprint-file-section.component.scss b/src/app/features/preprints/components/preprint-details/preprint-file-section/preprint-file-section.component.scss index c205f2091..7aa21b9e3 100644 --- a/src/app/features/preprints/components/preprint-details/preprint-file-section/preprint-file-section.component.scss +++ b/src/app/features/preprints/components/preprint-details/preprint-file-section/preprint-file-section.component.scss @@ -4,7 +4,7 @@ } .file-section-height { - min-height: 400px; + min-height: 600px; } .card { diff --git a/src/app/features/preprints/components/preprint-details/preprint-make-decision/preprint-make-decision.component.spec.ts b/src/app/features/preprints/components/preprint-details/preprint-make-decision/preprint-make-decision.component.spec.ts index 1e76617aa..35b404aed 100644 --- a/src/app/features/preprints/components/preprint-details/preprint-make-decision/preprint-make-decision.component.spec.ts +++ b/src/app/features/preprints/components/preprint-details/preprint-make-decision/preprint-make-decision.component.spec.ts @@ -47,10 +47,6 @@ describe('PreprintMakeDecisionComponent', () => { fixture.componentRef.setInput('isPendingWithdrawal', false); }); - it('should create', () => { - expect(component).toBeTruthy(); - }); - it('should return preprint from store', () => { const preprint = component.preprint(); expect(preprint).toBe(mockPreprint); diff --git a/src/app/features/preprints/components/preprint-details/preprint-metrics-info/preprint-metrics-info.component.html b/src/app/features/preprints/components/preprint-details/preprint-metrics-info/preprint-metrics-info.component.html index 9f44a3f6b..6ee03c0f4 100644 --- a/src/app/features/preprints/components/preprint-details/preprint-metrics-info/preprint-metrics-info.component.html +++ b/src/app/features/preprints/components/preprint-details/preprint-metrics-info/preprint-metrics-info.component.html @@ -1,5 +1,11 @@ - @if (metrics()) { + @if (isLoading()) { +
+ + + +
+ } @else if (metrics()) {
{{ 'preprints.details.share.views' | translate }}: {{ metrics()!.views }} | diff --git a/src/app/features/preprints/components/preprint-details/preprint-metrics-info/preprint-metrics-info.component.ts b/src/app/features/preprints/components/preprint-details/preprint-metrics-info/preprint-metrics-info.component.ts index c9e8219b9..ce1ee2b84 100644 --- a/src/app/features/preprints/components/preprint-details/preprint-metrics-info/preprint-metrics-info.component.ts +++ b/src/app/features/preprints/components/preprint-details/preprint-metrics-info/preprint-metrics-info.component.ts @@ -1,6 +1,7 @@ import { TranslatePipe } from '@ngx-translate/core'; import { Card } from 'primeng/card'; +import { Skeleton } from 'primeng/skeleton'; import { ChangeDetectionStrategy, Component, input } from '@angular/core'; @@ -8,11 +9,12 @@ import { PreprintMetrics } from '@osf/features/preprints/models'; @Component({ selector: 'osf-preprint-metrics-info', - imports: [Card, TranslatePipe], + imports: [Card, TranslatePipe, Skeleton], templateUrl: './preprint-metrics-info.component.html', styleUrl: './preprint-metrics-info.component.scss', changeDetection: ChangeDetectionStrategy.OnPush, }) export class PreprintMetricsInfoComponent { - metrics = input(); + metrics = input(); + isLoading = input(false); } diff --git a/src/app/features/preprints/components/preprint-details/preprint-tombstone/preprint-tombstone.component.html b/src/app/features/preprints/components/preprint-details/preprint-tombstone/preprint-tombstone.component.html index 901890563..a9423933d 100644 --- a/src/app/features/preprints/components/preprint-details/preprint-tombstone/preprint-tombstone.component.html +++ b/src/app/features/preprints/components/preprint-details/preprint-tombstone/preprint-tombstone.component.html @@ -13,11 +13,12 @@

{{ 'preprints.details.reasonForWithdrawal' | translate }}

{{ 'preprints.preprintStepper.review.sections.metadata.authors' | translate }}

- - - @if (areContributorsLoading()) { - - } +
@@ -32,18 +33,9 @@

{{ 'preprints.preprintStepper.common.labels.abstract' | translate }}

/>
-

{{ 'preprints.preprintStepper.review.sections.metadata.license' | translate }}

+

{{ 'common.labels.license' | translate }}

- - - -

{{ license()!.name }}

-
- -

{{ license()!.text | interpolate: licenseOptionsRecord() }}

-
-
-
+
@@ -51,6 +43,7 @@

{{ 'preprints.preprintStepper.review.sections.metadata.license' | translate

{{ 'common.labels.dateCreated' | translate }}

{{ preprintValue.dateCreated | date: 'MMM d, y, hh:mm a' }}

+

{{ 'common.labels.dateUpdated' | translate }}

{{ preprintValue.dateModified | date: 'MMM d, y, hh:mm a' }}

diff --git a/src/app/features/preprints/components/preprint-details/preprint-tombstone/preprint-tombstone.component.spec.ts b/src/app/features/preprints/components/preprint-details/preprint-tombstone/preprint-tombstone.component.spec.ts index ecd1053a5..8b8a19bd3 100644 --- a/src/app/features/preprints/components/preprint-details/preprint-tombstone/preprint-tombstone.component.spec.ts +++ b/src/app/features/preprints/components/preprint-details/preprint-tombstone/preprint-tombstone.component.spec.ts @@ -6,7 +6,8 @@ import { PreprintProviderDetails } from '@osf/features/preprints/models'; import { PreprintSelectors } from '@osf/features/preprints/store/preprint'; import { ContributorsListComponent, TruncatedTextComponent } from '@osf/shared/components'; import { InterpolatePipe } from '@osf/shared/pipes'; -import { ContributorsSelectors, SubjectsSelectors } from '@osf/shared/stores'; +import { ContributorsSelectors } from '@osf/shared/stores/contributors'; +import { SubjectsSelectors } from '@osf/shared/stores/subjects'; import { PreprintDoiSectionComponent } from '../preprint-doi-section/preprint-doi-section.component'; @@ -50,11 +51,15 @@ describe('PreprintTombstoneComponent', () => { value: false, }, { - selector: ContributorsSelectors.getContributors, + selector: ContributorsSelectors.getBibliographicContributors, value: mockContributors, }, { - selector: ContributorsSelectors.isContributorsLoading, + selector: ContributorsSelectors.isBibliographicContributorsLoading, + value: false, + }, + { + selector: ContributorsSelectors.hasMoreBibliographicContributors, value: false, }, { @@ -76,16 +81,6 @@ describe('PreprintTombstoneComponent', () => { fixture.componentRef.setInput('preprintProvider', mockProvider); }); - it('should create', () => { - expect(component).toBeTruthy(); - }); - - it('should compute bibliographic contributors', () => { - const bibliographicContributors = component.bibliographicContributors(); - expect(bibliographicContributors).toHaveLength(1); - expect(bibliographicContributors[0].isBibliographic).toBe(true); - }); - it('should compute license from preprint', () => { const license = component.license(); expect(license).toBe(mockPreprint.embeddedLicense); diff --git a/src/app/features/preprints/components/preprint-details/preprint-tombstone/preprint-tombstone.component.ts b/src/app/features/preprints/components/preprint-details/preprint-tombstone/preprint-tombstone.component.ts index 347a8a2ad..34f587648 100644 --- a/src/app/features/preprints/components/preprint-details/preprint-tombstone/preprint-tombstone.component.ts +++ b/src/app/features/preprints/components/preprint-details/preprint-tombstone/preprint-tombstone.component.ts @@ -2,7 +2,6 @@ import { createDispatchMap, select } from '@ngxs/store'; import { TranslatePipe } from '@ngx-translate/core'; -import { Accordion, AccordionContent, AccordionHeader, AccordionPanel } from 'primeng/accordion'; import { Card } from 'primeng/card'; import { Skeleton } from 'primeng/skeleton'; import { Tag } from 'primeng/tag'; @@ -14,16 +13,15 @@ import { Router } from '@angular/router'; import { ApplicabilityStatus, PreregLinkInfo } from '@osf/features/preprints/enums'; import { PreprintProviderDetails } from '@osf/features/preprints/models'; import { FetchPreprintById, PreprintSelectors } from '@osf/features/preprints/store/preprint'; -import { ContributorsListComponent, TruncatedTextComponent } from '@osf/shared/components'; +import { ContributorsListComponent, LicenseDisplayComponent, TruncatedTextComponent } from '@osf/shared/components'; import { ResourceType } from '@osf/shared/enums'; -import { InterpolatePipe } from '@osf/shared/pipes'; import { ContributorsSelectors, - FetchSelectedSubjects, - GetAllContributors, + GetBibliographicContributors, + LoadMoreBibliographicContributors, ResetContributorsState, - SubjectsSelectors, -} from '@osf/shared/stores'; +} from '@osf/shared/stores/contributors'; +import { FetchSelectedSubjects, SubjectsSelectors } from '@osf/shared/stores/subjects'; import { PreprintDoiSectionComponent } from '../preprint-doi-section/preprint-doi-section.component'; @@ -35,14 +33,10 @@ import { PreprintDoiSectionComponent } from '../preprint-doi-section/preprint-do Skeleton, TranslatePipe, TruncatedTextComponent, - Accordion, - AccordionContent, Tag, - AccordionPanel, - AccordionHeader, - InterpolatePipe, DatePipe, ContributorsListComponent, + LicenseDisplayComponent, ], templateUrl: './preprint-tombstone.component.html', styleUrl: './preprint-tombstone.component.scss', @@ -53,10 +47,11 @@ export class PreprintTombstoneComponent implements OnDestroy { readonly PreregLinkInfo = PreregLinkInfo; private actions = createDispatchMap({ - getContributors: GetAllContributors, + getBibliographicContributors: GetBibliographicContributors, resetContributorsState: ResetContributorsState, fetchPreprintById: FetchPreprintById, fetchSubjects: FetchSelectedSubjects, + loadMoreBibliographicContributors: LoadMoreBibliographicContributors, }); private router = inject(Router); @@ -67,9 +62,9 @@ export class PreprintTombstoneComponent implements OnDestroy { preprint = select(PreprintSelectors.getPreprint); isPreprintLoading = select(PreprintSelectors.isPreprintLoading); - contributors = select(ContributorsSelectors.getContributors); - areContributorsLoading = select(ContributorsSelectors.isContributorsLoading); - bibliographicContributors = computed(() => this.contributors().filter((contributor) => contributor.isBibliographic)); + bibliographicContributors = select(ContributorsSelectors.getBibliographicContributors); + areContributorsLoading = select(ContributorsSelectors.isBibliographicContributorsLoading); + hasMoreBibliographicContributors = select(ContributorsSelectors.hasMoreBibliographicContributors); subjects = select(SubjectsSelectors.getSelectedSubjects); areSelectedSubjectsLoading = select(SubjectsSelectors.areSelectedSubjectsLoading); @@ -88,7 +83,7 @@ export class PreprintTombstoneComponent implements OnDestroy { const preprint = this.preprint(); if (!preprint) return; - this.actions.getContributors(this.preprint()!.id, ResourceType.Preprint); + this.actions.getBibliographicContributors(this.preprint()?.id, ResourceType.Preprint); this.actions.fetchSubjects(this.preprint()!.id, ResourceType.Preprint); }); } @@ -100,4 +95,8 @@ export class PreprintTombstoneComponent implements OnDestroy { tagClicked(tag: string) { this.router.navigate(['/search'], { queryParams: { search: tag } }); } + + loadMoreContributors(): void { + this.actions.loadMoreBibliographicContributors(this.preprint()?.id, ResourceType.Preprint); + } } diff --git a/src/app/features/preprints/components/preprint-details/status-banner/status-banner.component.spec.ts b/src/app/features/preprints/components/preprint-details/status-banner/status-banner.component.spec.ts index ba1771f3c..449cd76e7 100644 --- a/src/app/features/preprints/components/preprint-details/status-banner/status-banner.component.spec.ts +++ b/src/app/features/preprints/components/preprint-details/status-banner/status-banner.component.spec.ts @@ -4,7 +4,7 @@ import { TitleCasePipe } from '@angular/common'; import { ComponentFixture, TestBed } from '@angular/core/testing'; import { ReviewAction } from '@osf/features/moderation/models'; -import { Preprint, PreprintProviderDetails } from '@osf/features/preprints/models'; +import { PreprintModel, PreprintProviderDetails } from '@osf/features/preprints/models'; import { PreprintSelectors } from '@osf/features/preprints/store/preprint'; import { IconComponent } from '@shared/components'; @@ -20,7 +20,7 @@ describe('StatusBannerComponent', () => { let component: StatusBannerComponent; let fixture: ComponentFixture; - const mockPreprint: Preprint = PREPRINT_MOCK; + const mockPreprint: PreprintModel = PREPRINT_MOCK; const mockProvider: PreprintProviderDetails = PREPRINT_PROVIDER_DETAILS_MOCK; const mockReviewAction: ReviewAction = REVIEW_ACTION_MOCK; const mockRequestAction = REVIEW_ACTION_MOCK; diff --git a/src/app/features/preprints/components/preprint-details/status-banner/status-banner.component.ts b/src/app/features/preprints/components/preprint-details/status-banner/status-banner.component.ts index b691f77d8..693a527ab 100644 --- a/src/app/features/preprints/components/preprint-details/status-banner/status-banner.component.ts +++ b/src/app/features/preprints/components/preprint-details/status-banner/status-banner.component.ts @@ -22,7 +22,7 @@ import { import { ProviderReviewsWorkflow, ReviewsState } from '@osf/features/preprints/enums'; import { PreprintProviderDetails, PreprintRequestAction } from '@osf/features/preprints/models'; import { PreprintSelectors } from '@osf/features/preprints/store/preprint'; -import { IconComponent } from '@shared/components'; +import { IconComponent } from '@osf/shared/components'; @Component({ selector: 'osf-preprint-status-banner', diff --git a/src/app/features/preprints/components/preprint-details/withdraw-dialog/withdraw-dialog.component.spec.ts b/src/app/features/preprints/components/preprint-details/withdraw-dialog/withdraw-dialog.component.spec.ts index 57d0e0094..bfd79e403 100644 --- a/src/app/features/preprints/components/preprint-details/withdraw-dialog/withdraw-dialog.component.spec.ts +++ b/src/app/features/preprints/components/preprint-details/withdraw-dialog/withdraw-dialog.component.spec.ts @@ -7,7 +7,7 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; import { formInputLimits } from '@osf/features/preprints/constants'; import { ProviderReviewsWorkflow, ReviewsState } from '@osf/features/preprints/enums'; -import { Preprint, PreprintProviderDetails } from '@osf/features/preprints/models'; +import { PreprintModel, PreprintProviderDetails } from '@osf/features/preprints/models'; import { WithdrawDialogComponent } from './withdraw-dialog.component'; @@ -23,7 +23,7 @@ describe('WithdrawDialogComponent', () => { let dialogConfigMock: any; const mockProvider: PreprintProviderDetails = PREPRINT_PROVIDER_DETAILS_MOCK; - const mockPreprint: Preprint = PREPRINT_MOCK; + const mockPreprint: PreprintModel = PREPRINT_MOCK; beforeEach(async () => { dialogRefMock = { diff --git a/src/app/features/preprints/components/preprint-details/withdraw-dialog/withdraw-dialog.component.ts b/src/app/features/preprints/components/preprint-details/withdraw-dialog/withdraw-dialog.component.ts index 14a50aee9..53a683cb4 100644 --- a/src/app/features/preprints/components/preprint-details/withdraw-dialog/withdraw-dialog.component.ts +++ b/src/app/features/preprints/components/preprint-details/withdraw-dialog/withdraw-dialog.component.ts @@ -15,7 +15,7 @@ import { ENVIRONMENT } from '@core/provider/environment.provider'; import { formInputLimits } from '@osf/features/preprints/constants'; import { ProviderReviewsWorkflow, ReviewsState } from '@osf/features/preprints/enums'; import { getPreprintDocumentType } from '@osf/features/preprints/helpers'; -import { Preprint, PreprintProviderDetails, PreprintWordGrammar } from '@osf/features/preprints/models'; +import { PreprintModel, PreprintProviderDetails, PreprintWordGrammar } from '@osf/features/preprints/models'; import { WithdrawPreprint } from '@osf/features/preprints/store/preprint'; import { CustomValidators } from '@osf/shared/helpers'; import { INPUT_VALIDATION_MESSAGES } from '@shared/constants'; @@ -37,7 +37,7 @@ export class WithdrawDialogComponent implements OnInit { readonly supportEmail = this.environment.supportEmail; private provider!: PreprintProviderDetails; - private preprint!: Preprint; + private preprint!: PreprintModel; private actions = createDispatchMap({ withdrawPreprint: WithdrawPreprint, diff --git a/src/app/features/preprints/components/stepper/author-assertion-step/author-assertions-step.component.spec.ts b/src/app/features/preprints/components/stepper/author-assertion-step/author-assertions-step.component.spec.ts index 22fdaa745..64abf505c 100644 --- a/src/app/features/preprints/components/stepper/author-assertion-step/author-assertions-step.component.spec.ts +++ b/src/app/features/preprints/components/stepper/author-assertion-step/author-assertions-step.component.spec.ts @@ -4,7 +4,7 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; import { ArrayInputComponent } from '@osf/features/preprints/components/stepper/author-assertion-step/array-input/array-input.component'; import { ApplicabilityStatus } from '@osf/features/preprints/enums'; -import { Preprint } from '@osf/features/preprints/models'; +import { PreprintModel } from '@osf/features/preprints/models'; import { PreprintStepperSelectors } from '@osf/features/preprints/store/preprint-stepper'; import { FormSelectComponent } from '@shared/components'; import { CustomConfirmationService, ToastService } from '@shared/services'; @@ -24,7 +24,7 @@ describe('AuthorAssertionsStepComponent', () => { let toastServiceMock: ReturnType; let customConfirmationServiceMock: ReturnType; - const mockPreprint: Preprint = PREPRINT_MOCK; + const mockPreprint: PreprintModel = PREPRINT_MOCK; beforeEach(async () => { toastServiceMock = ToastServiceMockBuilder.create().build(); diff --git a/src/app/features/preprints/components/stepper/author-assertion-step/author-assertions-step.component.ts b/src/app/features/preprints/components/stepper/author-assertion-step/author-assertions-step.component.ts index 71346a1f3..5a16cbd9e 100644 --- a/src/app/features/preprints/components/stepper/author-assertion-step/author-assertions-step.component.ts +++ b/src/app/features/preprints/components/stepper/author-assertion-step/author-assertions-step.component.ts @@ -26,7 +26,7 @@ import { import { ArrayInputComponent } from '@osf/features/preprints/components/stepper/author-assertion-step/array-input/array-input.component'; import { formInputLimits, preregLinksOptions } from '@osf/features/preprints/constants'; import { ApplicabilityStatus, PreregLinkInfo } from '@osf/features/preprints/enums'; -import { Preprint } from '@osf/features/preprints/models'; +import { PreprintModel } from '@osf/features/preprints/models'; import { PreprintStepperSelectors, UpdatePreprint } from '@osf/features/preprints/store/preprint-stepper'; import { CustomValidators, findChangedFields } from '@osf/shared/helpers'; import { FormSelectComponent } from '@shared/components'; @@ -228,7 +228,7 @@ export class AuthorAssertionsStepComponent { backButtonClicked() { const formValue = this.authorAssertionsForm.getRawValue(); - const changedFields = findChangedFields(formValue, this.createdPreprint()!); + const changedFields = findChangedFields(formValue, this.createdPreprint()!); if (!Object.keys(changedFields).length) { this.backClicked.emit(); diff --git a/src/app/features/preprints/components/stepper/file-step/file-step.component.spec.ts b/src/app/features/preprints/components/stepper/file-step/file-step.component.spec.ts index b7f4de625..0163a1125 100644 --- a/src/app/features/preprints/components/stepper/file-step/file-step.component.spec.ts +++ b/src/app/features/preprints/components/stepper/file-step/file-step.component.spec.ts @@ -3,7 +3,7 @@ import { MockComponents, MockProvider } from 'ng-mocks'; import { ComponentFixture, TestBed } from '@angular/core/testing'; import { PreprintFileSource } from '@osf/features/preprints/enums'; -import { Preprint, PreprintProviderDetails } from '@osf/features/preprints/models'; +import { PreprintModel, PreprintProviderDetails } from '@osf/features/preprints/models'; import { PreprintStepperSelectors } from '@osf/features/preprints/store/preprint-stepper'; import { FilesTreeComponent, IconComponent } from '@shared/components'; import { FileFolderModel } from '@shared/models'; @@ -26,7 +26,7 @@ describe('FileStepComponent', () => { let confirmationServiceMock: ReturnType; const mockProvider: PreprintProviderDetails = PREPRINT_PROVIDER_DETAILS_MOCK; - const mockPreprint: Preprint = PREPRINT_MOCK; + const mockPreprint: PreprintModel = PREPRINT_MOCK; const mockProjectFiles: FileFolderModel[] = [OSF_FILE_MOCK]; const mockPreprintFile: FileFolderModel = OSF_FILE_MOCK; diff --git a/src/app/features/preprints/components/stepper/preprints-metadata-step/preprints-affiliated-institutions/preprints-affiliated-institutions.component.ts b/src/app/features/preprints/components/stepper/preprints-metadata-step/preprints-affiliated-institutions/preprints-affiliated-institutions.component.ts index 4d57e1de4..06bab1f5b 100644 --- a/src/app/features/preprints/components/stepper/preprints-metadata-step/preprints-affiliated-institutions/preprints-affiliated-institutions.component.ts +++ b/src/app/features/preprints/components/stepper/preprints-metadata-step/preprints-affiliated-institutions/preprints-affiliated-institutions.component.ts @@ -7,7 +7,7 @@ import { Card } from 'primeng/card'; import { ChangeDetectionStrategy, Component, effect, input, OnInit, signal } from '@angular/core'; import { ReviewsState } from '@osf/features/preprints/enums'; -import { Preprint, PreprintProviderDetails } from '@osf/features/preprints/models'; +import { PreprintModel, PreprintProviderDetails } from '@osf/features/preprints/models'; import { PreprintStepperSelectors, SetInstitutionsChanged } from '@osf/features/preprints/store/preprint-stepper'; import { AffiliatedInstitutionSelectComponent } from '@osf/shared/components'; import { ResourceType } from '@osf/shared/enums'; @@ -28,7 +28,7 @@ import { }) export class PreprintsAffiliatedInstitutionsComponent implements OnInit { provider = input.required(); - preprint = input.required(); + preprint = input.required(); selectedInstitutions = signal([]); diff --git a/src/app/features/preprints/components/stepper/preprints-metadata-step/preprints-contributors/preprints-contributors.component.html b/src/app/features/preprints/components/stepper/preprints-metadata-step/preprints-contributors/preprints-contributors.component.html index 87e081260..c5dab158f 100644 --- a/src/app/features/preprints/components/stepper/preprints-metadata-step/preprints-contributors/preprints-contributors.component.html +++ b/src/app/features/preprints/components/stepper/preprints-metadata-step/preprints-contributors/preprints-contributors.component.html @@ -12,8 +12,9 @@

{{ 'project.overview.metadata.contributors' | translate }}

[(contributors)]="contributors" [tableParams]="tableParams()" [isLoading]="isContributorsLoading()" + [isLoadingMore]="isLoadingMore()" (remove)="removeContributor($event)" - (pageChanged)="pageChanged($event)" + (loadMore)="loadMoreContributors()" />
@if (hasChanges) { diff --git a/src/app/features/preprints/components/stepper/preprints-metadata-step/preprints-contributors/preprints-contributors.component.spec.ts b/src/app/features/preprints/components/stepper/preprints-metadata-step/preprints-contributors/preprints-contributors.component.spec.ts index abf7884cf..68481f6c6 100644 --- a/src/app/features/preprints/components/stepper/preprints-metadata-step/preprints-contributors/preprints-contributors.component.spec.ts +++ b/src/app/features/preprints/components/stepper/preprints-metadata-step/preprints-contributors/preprints-contributors.component.spec.ts @@ -6,7 +6,7 @@ import { UserSelectors } from '@core/store/user'; import { ContributorsTableComponent } from '@osf/shared/components/contributors'; import { ContributorModel } from '@shared/models'; import { CustomConfirmationService, CustomDialogService, ToastService } from '@shared/services'; -import { ContributorsSelectors } from '@shared/stores'; +import { ContributorsSelectors } from '@shared/stores/contributors'; import { PreprintsContributorsComponent } from './preprints-contributors.component'; diff --git a/src/app/features/preprints/components/stepper/preprints-metadata-step/preprints-contributors/preprints-contributors.component.ts b/src/app/features/preprints/components/stepper/preprints-metadata-step/preprints-contributors/preprints-contributors.component.ts index b2c08a55f..d11782ce5 100644 --- a/src/app/features/preprints/components/stepper/preprints-metadata-step/preprints-contributors/preprints-contributors.component.ts +++ b/src/app/features/preprints/components/stepper/preprints-metadata-step/preprints-contributors/preprints-contributors.component.ts @@ -5,7 +5,7 @@ import { TranslatePipe } from '@ngx-translate/core'; import { Button } from 'primeng/button'; import { Card } from 'primeng/card'; import { Message } from 'primeng/message'; -import { TableModule, TablePageEvent } from 'primeng/table'; +import { TableModule } from 'primeng/table'; import { filter } from 'rxjs'; @@ -41,7 +41,8 @@ import { ContributorsSelectors, DeleteContributor, GetAllContributors, -} from '@osf/shared/stores'; + LoadMoreContributors, +} from '@osf/shared/stores/contributors'; @Component({ selector: 'osf-preprints-contributors', @@ -63,14 +64,15 @@ export class PreprintsContributorsComponent implements OnInit { contributors = signal([]); contributorsTotalCount = select(ContributorsSelectors.getContributorsTotalCount); isContributorsLoading = select(ContributorsSelectors.isContributorsLoading); - page = select(ContributorsSelectors.getContributorsPageNumber); + isLoadingMore = select(ContributorsSelectors.isContributorsLoadingMore); pageSize = select(ContributorsSelectors.getContributorsPageSize); readonly tableParams = computed(() => ({ ...DEFAULT_TABLE_PARAMS, totalRecords: this.contributorsTotalCount(), - paginator: this.contributorsTotalCount() > DEFAULT_TABLE_PARAMS.rows, - firstRowIndex: (this.page() - 1) * this.pageSize(), + paginator: false, + scrollable: true, + firstRowIndex: 0, rows: this.pageSize(), })); @@ -80,6 +82,7 @@ export class PreprintsContributorsComponent implements OnInit { bulkUpdateContributors: BulkUpdateContributors, bulkAddContributors: BulkAddContributors, addContributor: AddContributor, + loadMoreContributors: LoadMoreContributors, }); get hasChanges(): boolean { @@ -112,13 +115,10 @@ export class PreprintsContributorsComponent implements OnInit { } openAddContributorDialog() { - const addedContributorIds = this.initialContributors().map((x) => x.userId); - this.customDialogService .open(AddContributorDialogComponent, { header: 'project.contributors.addDialog.addRegisteredContributor', width: '448px', - data: addedContributorIds, }) .onClose.pipe( filter((res: ContributorDialogAddModel) => !!res), @@ -182,10 +182,7 @@ export class PreprintsContributorsComponent implements OnInit { }); } - pageChanged(event: TablePageEvent) { - const page = Math.floor(event.first / event.rows) + 1; - const pageSize = event.rows; - - this.actions.getContributors(this.preprintId(), ResourceType.Preprint, page, pageSize); + loadMoreContributors(): void { + this.actions.loadMoreContributors(this.preprintId(), ResourceType.Preprint); } } diff --git a/src/app/features/preprints/components/stepper/preprints-metadata-step/preprints-metadata-step.component.ts b/src/app/features/preprints/components/stepper/preprints-metadata-step/preprints-metadata-step.component.ts index 786b4aaa0..b9ce03bba 100644 --- a/src/app/features/preprints/components/stepper/preprints-metadata-step/preprints-metadata-step.component.ts +++ b/src/app/features/preprints/components/stepper/preprints-metadata-step/preprints-metadata-step.component.ts @@ -13,7 +13,7 @@ import { ChangeDetectionStrategy, Component, inject, input, OnInit, output } fro import { FormControl, FormGroup, ReactiveFormsModule, Validators } from '@angular/forms'; import { formInputLimits } from '@osf/features/preprints/constants'; -import { MetadataForm, Preprint, PreprintProviderDetails } from '@osf/features/preprints/models'; +import { MetadataForm, PreprintModel, PreprintProviderDetails } from '@osf/features/preprints/models'; import { CreatePreprint, FetchLicenses, @@ -115,7 +115,7 @@ export class PreprintsMetadataStepComponent implements OnInit { const model = this.metadataForm.value; - const changedFields = findChangedFields(model, this.createdPreprint()!); + const changedFields = findChangedFields(model, this.createdPreprint()!); this.actions.updatePreprint(this.createdPreprint()!.id, changedFields).subscribe({ complete: () => { @@ -145,7 +145,7 @@ export class PreprintsMetadataStepComponent implements OnInit { backButtonClicked() { const formValue = this.metadataForm.value; delete formValue.subjects; - const changedFields = findChangedFields(formValue, this.createdPreprint()!); + const changedFields = findChangedFields(formValue, this.createdPreprint()!); if (!Object.keys(changedFields).length) { this.backClicked.emit(); diff --git a/src/app/features/preprints/components/stepper/preprints-metadata-step/preprints-subjects/preprints-subjects.component.spec.ts b/src/app/features/preprints/components/stepper/preprints-metadata-step/preprints-subjects/preprints-subjects.component.spec.ts index 578e1174a..42e91469b 100644 --- a/src/app/features/preprints/components/stepper/preprints-metadata-step/preprints-subjects/preprints-subjects.component.spec.ts +++ b/src/app/features/preprints/components/stepper/preprints-metadata-step/preprints-subjects/preprints-subjects.component.spec.ts @@ -6,7 +6,7 @@ import { FormControl } from '@angular/forms'; import { PreprintStepperSelectors } from '@osf/features/preprints/store/preprint-stepper'; import { SubjectsComponent } from '@osf/shared/components'; import { SubjectModel } from '@osf/shared/models'; -import { SubjectsSelectors } from '@osf/shared/stores'; +import { SubjectsSelectors } from '@osf/shared/stores/subjects'; import { PreprintsSubjectsComponent } from './preprints-subjects.component'; diff --git a/src/app/features/preprints/components/stepper/preprints-metadata-step/preprints-subjects/preprints-subjects.component.ts b/src/app/features/preprints/components/stepper/preprints-metadata-step/preprints-subjects/preprints-subjects.component.ts index b7d18cdbe..8551451f4 100644 --- a/src/app/features/preprints/components/stepper/preprints-metadata-step/preprints-subjects/preprints-subjects.component.ts +++ b/src/app/features/preprints/components/stepper/preprints-metadata-step/preprints-subjects/preprints-subjects.component.ts @@ -19,7 +19,7 @@ import { FetchSubjects, SubjectsSelectors, UpdateResourceSubjects, -} from '@osf/shared/stores'; +} from '@osf/shared/stores/subjects'; @Component({ selector: 'osf-preprints-subjects', diff --git a/src/app/features/preprints/components/stepper/review-step/review-step.component.html b/src/app/features/preprints/components/stepper/review-step/review-step.component.html index c4eb7d713..2ae83eb00 100644 --- a/src/app/features/preprints/components/stepper/review-step/review-step.component.html +++ b/src/app/features/preprints/components/stepper/review-step/review-step.component.html @@ -62,7 +62,12 @@

{{ 'preprints.preprintStepper.review.sections.metadata.title' | translate }}

{{ 'common.labels.contributors' | translate }}

- +
@if (affiliatedInstitutions().length) { @@ -71,18 +76,9 @@

{{ 'common.labels.contributors' | translate }}

@if (license()) {
-

{{ 'preprints.preprintStepper.review.sections.metadata.license' | translate }}

- - - - -

{{ license()?.name }}

-
- -

{{ license()!.text | interpolate: licenseOptionsRecord() }}

-
-
-
+

{{ 'common.labels.license' | translate }}

+ +
} diff --git a/src/app/features/preprints/components/stepper/review-step/review-step.component.spec.ts b/src/app/features/preprints/components/stepper/review-step/review-step.component.spec.ts index 16530d553..455f94667 100644 --- a/src/app/features/preprints/components/stepper/review-step/review-step.component.spec.ts +++ b/src/app/features/preprints/components/stepper/review-step/review-step.component.spec.ts @@ -8,7 +8,9 @@ import { PreprintProviderDetails } from '@osf/features/preprints/models'; import { PreprintStepperSelectors } from '@osf/features/preprints/store/preprint-stepper'; import { InterpolatePipe } from '@shared/pipes'; import { ToastService } from '@shared/services'; -import { ContributorsSelectors, InstitutionsSelectors, SubjectsSelectors } from '@shared/stores'; +import { ContributorsSelectors } from '@shared/stores/contributors'; +import { InstitutionsSelectors } from '@shared/stores/institutions'; +import { SubjectsSelectors } from '@shared/stores/subjects'; import { ReviewStepComponent } from './review-step.component'; @@ -52,7 +54,9 @@ describe('ReviewStepComponent', () => { { selector: PreprintStepperSelectors.isPreprintSubmitting, value: false }, { selector: PreprintStepperSelectors.getPreprintLicense, value: mockLicense }, { selector: PreprintStepperSelectors.getPreprintProject, value: mockPreprintProject }, - { selector: ContributorsSelectors.getContributors, value: mockContributors }, + { selector: ContributorsSelectors.getBibliographicContributors, value: mockContributors }, + { selector: ContributorsSelectors.isBibliographicContributorsLoading, value: false }, + { selector: ContributorsSelectors.hasMoreBibliographicContributors, value: false }, { selector: SubjectsSelectors.getSelectedSubjects, value: mockSubjects }, { selector: InstitutionsSelectors.getResourceInstitutions, value: mockInstitutions }, ], @@ -68,20 +72,10 @@ describe('ReviewStepComponent', () => { fixture.componentRef.setInput('provider', mockProvider); }); - it('should create', () => { - expect(component).toBeTruthy(); - }); - it('should have required provider input', () => { expect(component.provider()).toEqual(mockProvider); }); - it('should filter bibliographic contributors', () => { - const bibliographicContributors = component.bibliographicContributors(); - expect(bibliographicContributors).toHaveLength(1); - expect(bibliographicContributors.every((c) => c.isBibliographic)).toBe(true); - }); - it('should create license options record', () => { const licenseOptionsRecord = component.licenseOptionsRecord(); expect(licenseOptionsRecord).toEqual({ copyrightHolders: 'John Doe', year: '2023' }); diff --git a/src/app/features/preprints/components/stepper/review-step/review-step.component.ts b/src/app/features/preprints/components/stepper/review-step/review-step.component.ts index 132fa9dde..3a5d77919 100644 --- a/src/app/features/preprints/components/stepper/review-step/review-step.component.ts +++ b/src/app/features/preprints/components/stepper/review-step/review-step.component.ts @@ -2,7 +2,6 @@ import { createDispatchMap, select } from '@ngxs/store'; import { TranslatePipe } from '@ngx-translate/core'; -import { Accordion, AccordionContent, AccordionHeader, AccordionPanel } from 'primeng/accordion'; import { Button } from 'primeng/button'; import { Card } from 'primeng/card'; import { Tag } from 'primeng/tag'; @@ -26,13 +25,18 @@ import { import { AffiliatedInstitutionsViewComponent, ContributorsListComponent, + LicenseDisplayComponent, TruncatedTextComponent, } from '@shared/components'; import { ResourceType } from '@shared/enums'; -import { InterpolatePipe } from '@shared/pipes'; import { ToastService } from '@shared/services'; -import { ContributorsSelectors, FetchSelectedSubjects, GetAllContributors, SubjectsSelectors } from '@shared/stores'; +import { + ContributorsSelectors, + GetBibliographicContributors, + LoadMoreBibliographicContributors, +} from '@shared/stores/contributors'; import { FetchResourceInstitutions, InstitutionsSelectors } from '@shared/stores/institutions'; +import { FetchSelectedSubjects, SubjectsSelectors } from '@shared/stores/subjects'; @Component({ selector: 'osf-review-step', @@ -44,13 +48,9 @@ import { FetchResourceInstitutions, InstitutionsSelectors } from '@shared/stores Button, TitleCasePipe, TranslatePipe, - Accordion, - AccordionContent, - AccordionHeader, - AccordionPanel, - InterpolatePipe, AffiliatedInstitutionsViewComponent, ContributorsListComponent, + LicenseDisplayComponent, ], templateUrl: './review-step.component.html', styleUrl: './review-step.component.scss', @@ -60,7 +60,7 @@ export class ReviewStepComponent implements OnInit { private router = inject(Router); private toastService = inject(ToastService); private actions = createDispatchMap({ - getContributors: GetAllContributors, + getBibliographicContributors: GetBibliographicContributors, fetchSubjects: FetchSelectedSubjects, fetchLicenses: FetchLicenses, fetchPreprintProject: FetchPreprintProject, @@ -68,6 +68,7 @@ export class ReviewStepComponent implements OnInit { fetchResourceInstitutions: FetchResourceInstitutions, updatePrimaryFileRelationship: UpdatePrimaryFileRelationship, updatePreprint: UpdatePreprint, + loadMoreBibliographicContributors: LoadMoreBibliographicContributors, }); provider = input.required(); @@ -76,8 +77,9 @@ export class ReviewStepComponent implements OnInit { preprintFile = select(PreprintStepperSelectors.getPreprintFile); isPreprintSubmitting = select(PreprintStepperSelectors.isPreprintSubmitting); - contributors = select(ContributorsSelectors.getContributors); - bibliographicContributors = computed(() => this.contributors().filter((contributor) => contributor.isBibliographic)); + bibliographicContributors = select(ContributorsSelectors.getBibliographicContributors); + areContributorsLoading = select(ContributorsSelectors.isBibliographicContributorsLoading); + hasMoreBibliographicContributors = select(ContributorsSelectors.hasMoreBibliographicContributors); subjects = select(SubjectsSelectors.getSelectedSubjects); affiliatedInstitutions = select(InstitutionsSelectors.getResourceInstitutions); license = select(PreprintStepperSelectors.getPreprintLicense); @@ -88,7 +90,7 @@ export class ReviewStepComponent implements OnInit { readonly PreregLinkInfo = PreregLinkInfo; ngOnInit(): void { - this.actions.getContributors(this.preprint()!.id, ResourceType.Preprint); + this.actions.getBibliographicContributors(this.preprint()?.id, ResourceType.Preprint); this.actions.fetchSubjects(this.preprint()!.id, ResourceType.Preprint); this.actions.fetchLicenses(); this.actions.fetchPreprintProject(); @@ -123,4 +125,8 @@ export class ReviewStepComponent implements OnInit { cancelSubmission() { this.router.navigateByUrl('/preprints'); } + + loadMoreContributors(): void { + this.actions.loadMoreBibliographicContributors(this.preprint()?.id, ResourceType.Preprint); + } } diff --git a/src/app/features/preprints/mappers/preprints.mapper.ts b/src/app/features/preprints/mappers/preprints.mapper.ts index 4c8a6e630..0c7c4dfdf 100644 --- a/src/app/features/preprints/mappers/preprints.mapper.ts +++ b/src/app/features/preprints/mappers/preprints.mapper.ts @@ -1,13 +1,13 @@ -import { LicensesMapper } from '@osf/shared/mappers'; +import { IdentifiersMapper, LicensesMapper } from '@osf/shared/mappers'; import { ApiData, JsonApiResponseWithMeta, ResponseJsonApi } from '@osf/shared/models'; import { StringOrNull } from '@shared/helpers'; import { - Preprint, PreprintAttributesJsonApi, PreprintEmbedsJsonApi, PreprintLinksJsonApi, PreprintMetaJsonApi, + PreprintModel, PreprintRelationshipsJsonApi, PreprintShortInfoWithTotalCount, } from '../models'; @@ -35,7 +35,7 @@ export class PreprintsMapper { static fromPreprintJsonApi( response: ApiData - ): Preprint { + ): PreprintModel { return { id: response.id, dateCreated: response.attributes.date_created, @@ -78,6 +78,7 @@ export class PreprintsMapper { preregLinkInfo: response.attributes.prereg_link_info, preprintDoiLink: response.links.preprint_doi, articleDoiLink: response.links.doi, + embeddedLicense: null, }; } @@ -87,10 +88,10 @@ export class PreprintsMapper { PreprintMetaJsonApi, null > - ): Preprint { + ): PreprintModel { const data = response.data; - const meta = response.meta; const links = response.data.links; + return { id: data.id, dateCreated: data.attributes.date_created, @@ -131,17 +132,8 @@ export class PreprintsMapper { whyNoPrereg: data.attributes.why_no_prereg, preregLinks: data.attributes.prereg_links, preregLinkInfo: data.attributes.prereg_link_info, - metrics: { - downloads: meta.metrics.downloads, - views: meta.metrics.views, - }, - embeddedLicense: LicensesMapper.fromLicenseDataJsonApi(data.embeds.license.data), - identifiers: data.embeds.identifiers?.data.map((identifier) => ({ - id: identifier.id, - type: identifier.type, - value: identifier.attributes.value, - category: identifier.attributes.category, - })), + embeddedLicense: LicensesMapper.fromLicenseDataJsonApi(data.embeds?.license?.data), + identifiers: IdentifiersMapper.fromJsonApi(data.embeds?.identifiers), preprintDoiLink: links.preprint_doi, articleDoiLink: links.doi, }; diff --git a/src/app/features/preprints/models/preprint-json-api.models.ts b/src/app/features/preprints/models/preprint-json-api.models.ts index ca1456f4b..8dcc27bff 100644 --- a/src/app/features/preprints/models/preprint-json-api.models.ts +++ b/src/app/features/preprints/models/preprint-json-api.models.ts @@ -1,6 +1,11 @@ import { UserPermissions } from '@osf/shared/enums'; import { BooleanOrNull, StringOrNull } from '@osf/shared/helpers'; -import { ContributorDataJsonApi, LicenseRecordJsonApi, LicenseResponseJsonApi } from '@osf/shared/models'; +import { + ContributorDataJsonApi, + IdentifiersResponseJsonApi, + LicenseRecordJsonApi, + LicenseResponseJsonApi, +} from '@osf/shared/models'; import { ApplicabilityStatus, PreregLinkInfo, ReviewsState } from '../enums'; @@ -69,16 +74,7 @@ export interface PreprintEmbedsJsonApi { data: ContributorDataJsonApi[]; }; license: LicenseResponseJsonApi; - identifiers: { - data: { - id: string; - type: string; - attributes: { - category: string; - value: string; - }; - }[]; - }; + identifiers: IdentifiersResponseJsonApi; } export interface PreprintMetaJsonApi { diff --git a/src/app/features/preprints/models/preprint.models.ts b/src/app/features/preprints/models/preprint.models.ts index d425d17ff..22ceab8cc 100644 --- a/src/app/features/preprints/models/preprint.models.ts +++ b/src/app/features/preprints/models/preprint.models.ts @@ -4,7 +4,7 @@ import { Identifier, IdName, LicenseModel, LicenseOptions } from '@osf/shared/mo import { ApplicabilityStatus, PreregLinkInfo, ReviewsState } from '../enums'; -export interface Preprint { +export interface PreprintModel { id: string; dateCreated: string; dateModified: string; @@ -40,7 +40,7 @@ export interface Preprint { preregLinks: string[]; preregLinkInfo: PreregLinkInfo | null; metrics?: PreprintMetrics; - embeddedLicense?: LicenseModel; + embeddedLicense: LicenseModel | null; preprintDoiLink?: string; articleDoiLink?: string; identifiers?: Identifier[]; diff --git a/src/app/features/preprints/pages/my-preprints/my-preprints.component.spec.ts b/src/app/features/preprints/pages/my-preprints/my-preprints.component.spec.ts index cd271d446..e1bc36de0 100644 --- a/src/app/features/preprints/pages/my-preprints/my-preprints.component.spec.ts +++ b/src/app/features/preprints/pages/my-preprints/my-preprints.component.spec.ts @@ -7,11 +7,12 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; import { ActivatedRoute, Router } from '@angular/router'; import { PreprintShortInfo } from '@osf/features/preprints/models'; -import { PreprintSelectors } from '@osf/features/preprints/store/preprint'; import { ListInfoShortenerComponent, SearchInputComponent, SubHeaderComponent } from '@shared/components'; import { DEFAULT_TABLE_PARAMS } from '@shared/constants'; import { SortOrder } from '@shared/enums'; +import { MyPreprintsSelectors } from '../../store/my-preprints'; + import { MyPreprintsComponent } from './my-preprints.component'; import { PREPRINT_SHORT_INFO_ARRAY_MOCK } from '@testing/mocks/preprint-short-info.mock'; @@ -61,15 +62,15 @@ describe('MyPreprintsComponent', () => { provideMockStore({ signals: [ { - selector: PreprintSelectors.getMyPreprints, + selector: MyPreprintsSelectors.getMyPreprints, value: mockPreprints, }, { - selector: PreprintSelectors.getMyPreprintsTotalCount, + selector: MyPreprintsSelectors.getMyPreprintsTotalCount, value: 5, }, { - selector: PreprintSelectors.areMyPreprintsLoading, + selector: MyPreprintsSelectors.areMyPreprintsLoading, value: false, }, ], @@ -82,10 +83,6 @@ describe('MyPreprintsComponent', () => { fixture.detectChanges(); }); - it('should create', () => { - expect(component).toBeTruthy(); - }); - it('should initialize with correct default values', () => { expect(component.searchControl.value).toBe(''); expect(component.sortColumn()).toBe(''); diff --git a/src/app/features/preprints/pages/my-preprints/my-preprints.component.ts b/src/app/features/preprints/pages/my-preprints/my-preprints.component.ts index 76178fb3e..1e769f811 100644 --- a/src/app/features/preprints/pages/my-preprints/my-preprints.component.ts +++ b/src/app/features/preprints/pages/my-preprints/my-preprints.component.ts @@ -30,7 +30,7 @@ import { parseQueryFilterParams } from '@osf/shared/helpers'; import { QueryParams, SearchFilters, TableParameters } from '@osf/shared/models'; import { PreprintShortInfo } from '../../models'; -import { FetchMyPreprints, PreprintSelectors } from '../../store/preprint'; +import { FetchMyPreprints, MyPreprintsSelectors } from '../../store/my-preprints'; @Component({ selector: 'osf-my-preprints', @@ -65,9 +65,9 @@ export class MyPreprintsComponent { currentPageSize = signal(DEFAULT_TABLE_PARAMS.rows); tableParams = signal({ ...DEFAULT_TABLE_PARAMS, firstRowIndex: 0 }); - preprints = select(PreprintSelectors.getMyPreprints); - preprintsTotalCount = select(PreprintSelectors.getMyPreprintsTotalCount); - areMyPreprintsLoading = select(PreprintSelectors.areMyPreprintsLoading); + preprints = select(MyPreprintsSelectors.getMyPreprints); + preprintsTotalCount = select(MyPreprintsSelectors.getMyPreprintsTotalCount); + areMyPreprintsLoading = select(MyPreprintsSelectors.areMyPreprintsLoading); skeletonData: PreprintShortInfo[] = Array.from({ length: 10 }, () => ({}) as PreprintShortInfo); constructor() { diff --git a/src/app/features/preprints/pages/preprint-details/preprint-details.component.html b/src/app/features/preprints/pages/preprint-details/preprint-details.component.html index 38aaef76b..f987fa783 100644 --- a/src/app/features/preprints/pages/preprint-details/preprint-details.component.html +++ b/src/app/features/preprints/pages/preprint-details/preprint-details.component.html @@ -41,7 +41,7 @@

{{ preprint()?.title }}

- @if (isPreprintLoading() || isPreprintProviderLoading() || areContributorsLoading()) { + @if (isPreprintLoading() || isPreprintProviderLoading()) { @@ -83,6 +83,7 @@

{{ preprint()?.title }}

@if (isOsfPreprint()) { } + @if (moderationStatusBannerVisible()) { {{ preprint()?.title }} [latestWithdrawalRequest]="latestWithdrawalRequest()" /> } + @if (statusBannerVisible()) { {{ preprint()?.title }} (preprintVersionSelected)="fetchPreprint($event)" /> - + diff --git a/src/app/features/preprints/pages/preprint-details/preprint-details.component.spec.ts b/src/app/features/preprints/pages/preprint-details/preprint-details.component.spec.ts index cc766c833..78bd01e18 100644 --- a/src/app/features/preprints/pages/preprint-details/preprint-details.component.spec.ts +++ b/src/app/features/preprints/pages/preprint-details/preprint-details.component.spec.ts @@ -5,11 +5,10 @@ import { of } from 'rxjs'; import { ComponentFixture, TestBed } from '@angular/core/testing'; import { ActivatedRoute, Router } from '@angular/router'; -import { UserSelectors } from '@core/store/user'; import { UserPermissions } from '@osf/shared/enums'; import { CustomDialogService, MetaTagsService } from '@osf/shared/services'; import { DataciteService } from '@osf/shared/services/datacite/datacite.service'; -import { ContributorsSelectors } from '@osf/shared/stores'; +import { ContributorsSelectors } from '@osf/shared/stores/contributors'; import { AdditionalInfoComponent, @@ -28,7 +27,7 @@ import { PreprintProvidersSelectors } from '../../store/preprint-providers'; import { PreprintDetailsComponent } from './preprint-details.component'; -import { MOCK_CONTRIBUTOR, MOCK_USER } from '@testing/mocks'; +import { MOCK_CONTRIBUTOR } from '@testing/mocks'; import { PREPRINT_MOCK } from '@testing/mocks/preprint.mock'; import { PREPRINT_PROVIDER_DETAILS_MOCK } from '@testing/mocks/preprint-provider-details'; import { PREPRINT_REQUEST_MOCK } from '@testing/mocks/preprint-request.mock'; @@ -55,7 +54,6 @@ describe('PreprintDetailsComponent', () => { const mockWithdrawalRequests = [PREPRINT_REQUEST_MOCK]; const mockRequestActions = [REVIEW_ACTION_MOCK]; const mockContributors = [MOCK_CONTRIBUTOR]; - const mockCurrentUser = MOCK_USER; beforeEach(async () => { routerMock = RouterMockBuilder.create() @@ -104,10 +102,6 @@ describe('PreprintDetailsComponent', () => { MockProvider(CustomDialogService, mockCustomDialogService), provideMockStore({ signals: [ - { - selector: UserSelectors.getCurrentUser, - value: mockCurrentUser, - }, { selector: PreprintProvidersSelectors.getPreprintProviderDetails('osf'), value: mockProvider, @@ -156,6 +150,14 @@ describe('PreprintDetailsComponent', () => { selector: PreprintSelectors.arePreprintRequestActionsLoading, value: false, }, + { + selector: PreprintSelectors.hasAdminAccess, + value: false, + }, + { + selector: PreprintSelectors.hasWriteAccess, + value: false, + }, ], }), ], @@ -212,11 +214,6 @@ describe('PreprintDetailsComponent', () => { expect(contributors).toBe(mockContributors); }); - it('should return current user from store', () => { - const currentUser = component.currentUser(); - expect(currentUser).toBe(mockCurrentUser); - }); - it('should return contributors loading state from store', () => { const loading = component.areContributorsLoading(); expect(loading).toBe(false); @@ -338,21 +335,10 @@ describe('PreprintDetailsComponent', () => { }); it('should handle hasReadWriteAccess correctly', () => { - const hasAccess = component['hasReadWriteAccess'](); + const hasAccess = component['hasWriteAccess'](); expect(typeof hasAccess).toBe('boolean'); }); - it('should handle preprint with write permissions', () => { - const preprintWithWrite = { - ...mockPreprint, - currentUserPermissions: [UserPermissions.Write], - }; - jest.spyOn(component, 'preprint').mockReturnValue(preprintWithWrite); - - const hasAccess = component['hasReadWriteAccess'](); - expect(hasAccess).toBe(true); - }); - it('should handle preprint without write permissions', () => { const preprintWithoutWrite = { ...mockPreprint, @@ -360,7 +346,7 @@ describe('PreprintDetailsComponent', () => { }; jest.spyOn(component, 'preprint').mockReturnValue(preprintWithoutWrite); - const hasAccess = component['hasReadWriteAccess'](); + const hasAccess = component['hasWriteAccess'](); expect(hasAccess).toBe(false); }); }); diff --git a/src/app/features/preprints/pages/preprint-details/preprint-details.component.ts b/src/app/features/preprints/pages/preprint-details/preprint-details.component.ts index 8db420ff6..bbb0626fc 100644 --- a/src/app/features/preprints/pages/preprint-details/preprint-details.component.ts +++ b/src/app/features/preprints/pages/preprint-details/preprint-details.component.ts @@ -27,19 +27,6 @@ import { ENVIRONMENT } from '@core/provider/environment.provider'; import { HelpScoutService } from '@core/services/help-scout.service'; import { ClearCurrentProvider } from '@core/store/provider'; import { UserSelectors } from '@core/store/user'; -import { - AdditionalInfoComponent, - GeneralInformationComponent, - ModerationStatusBannerComponent, - PreprintFileSectionComponent, - PreprintMakeDecisionComponent, - PreprintMetricsInfoComponent, - PreprintTombstoneComponent, - ShareAndDownloadComponent, - StatusBannerComponent, - WithdrawDialogComponent, -} from '@osf/features/preprints/components'; -import { PreprintRequestMachineState, ProviderReviewsWorkflow, ReviewsState } from '@osf/features/preprints/enums'; import { FetchPreprintById, FetchPreprintRequestActions, @@ -51,13 +38,26 @@ import { import { GetPreprintProviderById, PreprintProvidersSelectors } from '@osf/features/preprints/store/preprint-providers'; import { CreateNewVersion, PreprintStepperSelectors } from '@osf/features/preprints/store/preprint-stepper'; import { pathJoin } from '@osf/shared/helpers'; -import { ReviewPermissions, UserPermissions } from '@shared/enums'; +import { ReviewPermissions } from '@shared/enums'; import { CustomDialogService, MetaTagsService, ToastService } from '@shared/services'; import { AnalyticsService } from '@shared/services/analytics.service'; import { DataciteService } from '@shared/services/datacite/datacite.service'; -import { ContributorsSelectors } from '@shared/stores'; +import { ContributorsSelectors } from '@shared/stores/contributors'; +import { + AdditionalInfoComponent, + GeneralInformationComponent, + ModerationStatusBannerComponent, + PreprintFileSectionComponent, + PreprintMakeDecisionComponent, + PreprintMetricsInfoComponent, + PreprintTombstoneComponent, + ShareAndDownloadComponent, + StatusBannerComponent, + WithdrawDialogComponent, +} from '../../components'; import { PreprintWarningBannerComponent } from '../../components/preprint-details/preprint-warning-banner/preprint-warning-banner.component'; +import { PreprintRequestMachineState, ProviderReviewsWorkflow, ReviewsState } from '../../enums'; @Component({ selector: 'osf-preprint-details', @@ -96,6 +96,8 @@ export class PreprintDetailsComponent implements OnInit, OnDestroy { private readonly metaTags = inject(MetaTagsService); private readonly datePipe = inject(DatePipe); private readonly dataciteService = inject(DataciteService); + private readonly analyticsService = inject(AnalyticsService); + private readonly environment = inject(ENVIRONMENT); private preprintId = toSignal(this.route.params.pipe(map((params) => params['id'])) ?? of(undefined)); @@ -110,6 +112,7 @@ export class PreprintDetailsComponent implements OnInit, OnDestroy { fetchPreprintRequestActions: FetchPreprintRequestActions, clearCurrentProvider: ClearCurrentProvider, }); + providerId = toSignal(this.route.params.pipe(map((params) => params['providerId'])) ?? of(undefined)); currentUser = select(UserSelectors.getCurrentUser); preprintProvider = select(PreprintProvidersSelectors.getPreprintProviderDetails(this.providerId())); @@ -125,8 +128,13 @@ export class PreprintDetailsComponent implements OnInit, OnDestroy { areWithdrawalRequestsLoading = select(PreprintSelectors.arePreprintRequestsLoading); requestActions = select(PreprintSelectors.getPreprintRequestActions); areRequestActionsLoading = select(PreprintSelectors.arePreprintRequestActionsLoading); + hasAdminAccess = select(PreprintSelectors.hasAdminAccess); + hasWriteAccess = select(PreprintSelectors.hasWriteAccess); + metrics = select(PreprintSelectors.getPreprintMetrics); + areMetricsLoading = select(PreprintSelectors.arePreprintMetricsLoading); isPresentModeratorQueryParam = toSignal(this.route.queryParams.pipe(map((params) => params['mode'] === 'moderator'))); + moderationMode = computed(() => { const provider = this.preprintProvider(); return this.isPresentModeratorQueryParam() && provider?.permissions.includes(ReviewPermissions.ViewSubmissions); @@ -156,37 +164,18 @@ export class PreprintDetailsComponent implements OnInit, OnDestroy { return actions[0]; }); - private readonly analyticsService = inject(AnalyticsService); - constructor() { this.helpScoutService.setResourceType('preprint'); + effect(() => { const currentPreprint = this.preprint(); + if (currentPreprint && currentPreprint.isPublic) { this.analyticsService.sendCountedUsage(currentPreprint.id, 'preprint.detail').subscribe(); } }); } - private currentUserIsAdmin = computed(() => { - return this.preprint()?.currentUserPermissions.includes(UserPermissions.Admin) || false; - }); - - private currentUserIsContributor = computed(() => { - const contributors = this.contributors(); - const currentUser = this.currentUser(); - - if (this.currentUserIsAdmin()) { - return true; - } else if (contributors.length) { - const authorIds = contributors.map((author) => author.id); - return currentUser?.id - ? authorIds.some((id) => id.endsWith(currentUser!.id)) && this.hasReadWriteAccess() - : false; - } - return false; - }); - private preprintWithdrawableState = computed(() => { const preprint = this.preprint(); if (!preprint) return false; @@ -197,7 +186,7 @@ export class PreprintDetailsComponent implements OnInit, OnDestroy { const preprint = this.preprint(); if (!preprint) return false; - return this.currentUserIsAdmin() && preprint.datePublished && preprint.isLatestVersion; + return this.hasAdminAccess() && preprint.datePublished && preprint.isLatestVersion; }); editButtonVisible = computed(() => { @@ -208,7 +197,7 @@ export class PreprintDetailsComponent implements OnInit, OnDestroy { const providerIsPremod = provider.reviewsWorkflow === ProviderReviewsWorkflow.PreModeration; const preprintIsRejected = preprint.reviewsState === ReviewsState.Rejected; - if (!this.currentUserIsContributor()) { + if (!this.hasWriteAccess()) { return false; } @@ -224,7 +213,7 @@ export class PreprintDetailsComponent implements OnInit, OnDestroy { return true; } - if (preprintIsRejected && this.currentUserIsAdmin()) { + if (preprintIsRejected && this.hasAdminAccess()) { return true; } } @@ -235,7 +224,7 @@ export class PreprintDetailsComponent implements OnInit, OnDestroy { const providerIsPremod = this.preprintProvider()?.reviewsWorkflow === ProviderReviewsWorkflow.PreModeration; const preprintIsRejected = this.preprint()?.reviewsState === ReviewsState.Rejected; - return providerIsPremod && preprintIsRejected && this.currentUserIsAdmin() + return providerIsPremod && preprintIsRejected && this.hasAdminAccess() ? 'common.buttons.editAndResubmit' : 'common.buttons.edit'; }); @@ -255,8 +244,9 @@ export class PreprintDetailsComponent implements OnInit, OnDestroy { withdrawalButtonVisible = computed(() => { if (this.areWithdrawalRequestsLoading() || this.areRequestActionsLoading()) return false; + return ( - this.currentUserIsAdmin() && + this.hasAdminAccess() && this.preprintWithdrawableState() && !this.isWithdrawalRejected() && !this.isPendingWithdrawal() @@ -293,18 +283,16 @@ export class PreprintDetailsComponent implements OnInit, OnDestroy { return ( provider.reviewsWorkflow && preprint.isPublic && - this.currentUserIsContributor() && + this.hasWriteAccess() && preprint.reviewsState !== ReviewsState.Initial && !preprint.isPreprintOrphan ); }); ngOnInit() { - this.actions.getPreprintProviderById(this.providerId()).subscribe({ - next: () => { - this.fetchPreprint(this.preprintId()); - }, - }); + this.actions.getPreprintProviderById(this.providerId()); + this.fetchPreprint(this.preprintId()); + this.dataciteService.logIdentifiableView(this.preprint$).pipe(takeUntilDestroyed(this.destroyRef)).subscribe(); } @@ -362,12 +350,15 @@ export class PreprintDetailsComponent implements OnInit, OnDestroy { this.actions.fetchPreprintById(preprintId).subscribe({ next: () => { this.checkAndSetVersionToTheUrl(); + if (this.preprint()!.currentUserPermissions.length > 0 || this.moderationMode()) { this.actions.fetchPreprintReviewActions(); - if (this.preprintWithdrawableState() && (this.currentUserIsAdmin() || this.moderationMode())) { + + if (this.preprintWithdrawableState() && (this.hasAdminAccess() || this.moderationMode())) { this.actions.fetchPreprintRequests().subscribe({ next: () => { const latestWithdrawalRequest = this.latestWithdrawalRequest(); + if (latestWithdrawalRequest) { this.actions.fetchPreprintRequestActions(latestWithdrawalRequest.id); } @@ -404,10 +395,6 @@ export class PreprintDetailsComponent implements OnInit, OnDestroy { ); } - private hasReadWriteAccess(): boolean { - return this.preprint()?.currentUserPermissions.includes(UserPermissions.Write) || false; - } - private checkAndSetVersionToTheUrl() { const currentUrl = this.router.url; const newPreprintId = this.preprint()!.id; diff --git a/src/app/features/preprints/preprints.routes.ts b/src/app/features/preprints/preprints.routes.ts index 93a9f8654..a1d8b12ad 100644 --- a/src/app/features/preprints/preprints.routes.ts +++ b/src/app/features/preprints/preprints.routes.ts @@ -9,7 +9,9 @@ import { PreprintState } from '@osf/features/preprints/store/preprint'; import { PreprintProvidersState } from '@osf/features/preprints/store/preprint-providers'; import { PreprintStepperState } from '@osf/features/preprints/store/preprint-stepper'; import { ConfirmLeavingGuard } from '@shared/guards'; -import { CitationsState, ContributorsState, ProjectsState, SubjectsState } from '@shared/stores'; +import { CitationsState } from '@shared/stores/citations'; +import { ProjectsState } from '@shared/stores/projects'; +import { SubjectsState } from '@shared/stores/subjects'; import { PreprintModerationState } from '../moderation/store/preprint-moderation'; @@ -18,14 +20,7 @@ export const preprintsRoutes: Routes = [ path: '', component: PreprintsComponent, providers: [ - provideStates([ - PreprintProvidersState, - PreprintStepperState, - ContributorsState, - SubjectsState, - PreprintState, - CitationsState, - ]), + provideStates([PreprintProvidersState, PreprintStepperState, SubjectsState, PreprintState, CitationsState]), ], children: [ { diff --git a/src/app/features/preprints/services/preprint-files.service.ts b/src/app/features/preprints/services/preprint-files.service.ts index 0191a7919..ef640e202 100644 --- a/src/app/features/preprints/services/preprint-files.service.ts +++ b/src/app/features/preprints/services/preprint-files.service.ts @@ -6,10 +6,10 @@ import { inject, Injectable } from '@angular/core'; import { ENVIRONMENT } from '@core/provider/environment.provider'; import { PreprintsMapper } from '@osf/features/preprints/mappers'; import { - Preprint, PreprintAttributesJsonApi, PreprintFilesLinks, PreprintLinksJsonApi, + PreprintModel, PreprintRelationshipsJsonApi, } from '@osf/features/preprints/models'; import { ApiData, FileFolderModel, FileFolderResponseJsonApi, FileFoldersResponseJsonApi } from '@osf/shared/models'; @@ -28,7 +28,7 @@ export class PreprintFilesService { return `${this.environment.apiDomainUrl}/v2`; } - updateFileRelationship(preprintId: string, fileId: string): Observable { + updateFileRelationship(preprintId: string, fileId: string): Observable { return this.jsonApiService .patch>( `${this.apiUrl}/preprints/${preprintId}/`, diff --git a/src/app/features/preprints/services/preprints-projects.service.ts b/src/app/features/preprints/services/preprints-projects.service.ts index da369b71e..e3cd53fad 100644 --- a/src/app/features/preprints/services/preprints-projects.service.ts +++ b/src/app/features/preprints/services/preprints-projects.service.ts @@ -14,7 +14,12 @@ import { import { JsonApiService } from '@osf/shared/services'; import { PreprintsMapper } from '../mappers'; -import { Preprint, PreprintAttributesJsonApi, PreprintLinksJsonApi, PreprintRelationshipsJsonApi } from '../models'; +import { + PreprintAttributesJsonApi, + PreprintLinksJsonApi, + PreprintModel, + PreprintRelationshipsJsonApi, +} from '../models'; @Injectable({ providedIn: 'root', @@ -62,7 +67,7 @@ export class PreprintsProjectsService { }); } - updatePreprintProjectRelationship(preprintId: string, projectId: string): Observable { + updatePreprintProjectRelationship(preprintId: string, projectId: string): Observable { return this.jsonApiService .patch>( `${this.apiUrl}/preprints/${preprintId}/`, diff --git a/src/app/features/preprints/services/preprints.service.ts b/src/app/features/preprints/services/preprints.service.ts index bbb327aed..ff55460ba 100644 --- a/src/app/features/preprints/services/preprints.service.ts +++ b/src/app/features/preprints/services/preprints.service.ts @@ -14,11 +14,11 @@ import { JsonApiService } from '@osf/shared/services'; import { preprintSortFieldMap } from '../constants'; import { PreprintRequestMapper, PreprintsMapper } from '../mappers'; import { - Preprint, PreprintAttributesJsonApi, PreprintEmbedsJsonApi, PreprintLinksJsonApi, PreprintMetaJsonApi, + PreprintModel, PreprintRelationshipsJsonApi, PreprintRequest, PreprintRequestActionsJsonApiResponse, @@ -79,11 +79,7 @@ export class PreprintsService { } getByIdWithEmbeds(id: string) { - const params = { - 'metrics[views]': 'total', - 'metrics[downloads]': 'total', - 'embed[]': ['license', 'identifiers'], - }; + const params = { 'embed[]': ['license', 'identifiers'] }; return this.jsonApiService .get< JsonApiResponseWithMeta< @@ -95,11 +91,26 @@ export class PreprintsService { .pipe(map((response) => PreprintsMapper.fromPreprintWithEmbedsJsonApi(response))); } + getPreprintMetrics(id: string) { + const params = { 'metrics[views]': 'total', 'metrics[downloads]': 'total' }; + + return this.jsonApiService + .get< + JsonApiResponseWithMeta, PreprintMetaJsonApi, null> + >(`${this.apiUrl}/preprints/${id}/`, params) + .pipe( + map((response) => ({ + downloads: response.meta.metrics.downloads, + views: response.meta.metrics.views, + })) + ); + } + deletePreprint(id: string) { return this.jsonApiService.delete(`${this.apiUrl}/preprints/${id}/`); } - updatePreprint(id: string, payload: Partial): Observable { + updatePreprint(id: string, payload: Partial): Observable { const apiPayload = this.mapPreprintDomainToApiPayload(payload); return this.jsonApiService @@ -132,7 +143,7 @@ export class PreprintsService { .pipe(map((response) => PreprintsMapper.fromPreprintJsonApi(response.data))); } - private mapPreprintDomainToApiPayload(domainPayload: Partial): Partial { + private mapPreprintDomainToApiPayload(domainPayload: Partial): Partial { const apiPayload: Record = {}; Object.entries(domainPayload).forEach(([key, value]) => { if (value !== undefined && this.domainToApiFieldMap[key]) { diff --git a/src/app/features/preprints/store/my-preprints/index.ts b/src/app/features/preprints/store/my-preprints/index.ts new file mode 100644 index 000000000..a83accbb1 --- /dev/null +++ b/src/app/features/preprints/store/my-preprints/index.ts @@ -0,0 +1,4 @@ +export * from './my-preprints.actions'; +export * from './my-preprints.model'; +export * from './my-preprints.selectors'; +export * from './my-preprints.state'; diff --git a/src/app/features/preprints/store/my-preprints/my-preprints.actions.ts b/src/app/features/preprints/store/my-preprints/my-preprints.actions.ts new file mode 100644 index 000000000..6718a06e7 --- /dev/null +++ b/src/app/features/preprints/store/my-preprints/my-preprints.actions.ts @@ -0,0 +1,11 @@ +import { SearchFilters } from '@shared/models'; + +export class FetchMyPreprints { + static readonly type = '[My Preprints] Fetch My Preprints'; + + constructor( + public pageNumber: number, + public pageSize: number, + public filters: SearchFilters + ) {} +} diff --git a/src/app/features/preprints/store/my-preprints/my-preprints.model.ts b/src/app/features/preprints/store/my-preprints/my-preprints.model.ts new file mode 100644 index 000000000..d2343a4ec --- /dev/null +++ b/src/app/features/preprints/store/my-preprints/my-preprints.model.ts @@ -0,0 +1,16 @@ +import { AsyncStateWithTotalCount } from '@shared/models'; + +import { PreprintShortInfo } from '../../models'; + +export interface MyPreprintsStateModel { + myPreprints: AsyncStateWithTotalCount; +} + +export const DEFAULT_MY_PREPRINTS_STATE: MyPreprintsStateModel = { + myPreprints: { + data: [], + isLoading: false, + error: null, + totalCount: 0, + }, +}; diff --git a/src/app/features/preprints/store/my-preprints/my-preprints.selectors.ts b/src/app/features/preprints/store/my-preprints/my-preprints.selectors.ts new file mode 100644 index 000000000..6a13f37ee --- /dev/null +++ b/src/app/features/preprints/store/my-preprints/my-preprints.selectors.ts @@ -0,0 +1,21 @@ +import { Selector } from '@ngxs/store'; + +import { MyPreprintsStateModel } from './my-preprints.model'; +import { MyPreprintsState } from './my-preprints.state'; + +export class MyPreprintsSelectors { + @Selector([MyPreprintsState]) + static getMyPreprints(state: MyPreprintsStateModel) { + return state.myPreprints.data; + } + + @Selector([MyPreprintsState]) + static getMyPreprintsTotalCount(state: MyPreprintsStateModel) { + return state.myPreprints.totalCount; + } + + @Selector([MyPreprintsState]) + static areMyPreprintsLoading(state: MyPreprintsStateModel) { + return state.myPreprints.isLoading; + } +} diff --git a/src/app/features/preprints/store/my-preprints/my-preprints.state.ts b/src/app/features/preprints/store/my-preprints/my-preprints.state.ts new file mode 100644 index 000000000..dc6bc45ce --- /dev/null +++ b/src/app/features/preprints/store/my-preprints/my-preprints.state.ts @@ -0,0 +1,43 @@ +import { Action, State, StateContext } from '@ngxs/store'; +import { patch } from '@ngxs/store/operators'; + +import { tap } from 'rxjs'; +import { catchError } from 'rxjs/operators'; + +import { inject, Injectable } from '@angular/core'; + +import { handleSectionError } from '@shared/helpers'; + +import { PreprintsService } from '../../services'; + +import { FetchMyPreprints } from './my-preprints.actions'; +import { DEFAULT_MY_PREPRINTS_STATE, MyPreprintsStateModel } from './my-preprints.model'; + +@State({ + name: 'myPreprints', + defaults: { ...DEFAULT_MY_PREPRINTS_STATE }, +}) +@Injectable() +export class MyPreprintsState { + private preprintsService = inject(PreprintsService); + + @Action(FetchMyPreprints) + fetchMyPreprints(ctx: StateContext, action: FetchMyPreprints) { + ctx.setState(patch({ myPreprints: patch({ isLoading: true }) })); + + return this.preprintsService.getMyPreprints(action.pageNumber, action.pageSize, action.filters).pipe( + tap((preprints) => { + ctx.setState( + patch({ + myPreprints: patch({ + isLoading: false, + data: preprints.data, + totalCount: preprints.totalCount, + }), + }) + ); + }), + catchError((error) => handleSectionError(ctx, 'myPreprints', error)) + ); + } +} diff --git a/src/app/features/preprints/store/preprint-stepper/preprint-stepper.actions.ts b/src/app/features/preprints/store/preprint-stepper/preprint-stepper.actions.ts index 62ee6f9b7..288638c23 100644 --- a/src/app/features/preprints/store/preprint-stepper/preprint-stepper.actions.ts +++ b/src/app/features/preprints/store/preprint-stepper/preprint-stepper.actions.ts @@ -1,5 +1,5 @@ import { PreprintFileSource } from '@osf/features/preprints/enums'; -import { Preprint } from '@osf/features/preprints/models'; +import { PreprintModel } from '@osf/features/preprints/models'; import { StringOrNull } from '@shared/helpers'; import { FileFolderModel, FileModel, LicenseOptions } from '@shared/models'; @@ -24,7 +24,7 @@ export class UpdatePreprint { constructor( public id: string, - public payload: Partial + public payload: Partial ) {} } diff --git a/src/app/features/preprints/store/preprint-stepper/preprint-stepper.model.ts b/src/app/features/preprints/store/preprint-stepper/preprint-stepper.model.ts index 4f6b7eb55..42261f965 100644 --- a/src/app/features/preprints/store/preprint-stepper/preprint-stepper.model.ts +++ b/src/app/features/preprints/store/preprint-stepper/preprint-stepper.model.ts @@ -1,12 +1,12 @@ import { PreprintFileSource } from '@osf/features/preprints/enums'; -import { Preprint, PreprintFilesLinks } from '@osf/features/preprints/models'; +import { PreprintFilesLinks, PreprintModel } from '@osf/features/preprints/models'; import { StringOrNull } from '@shared/helpers'; import { AsyncStateModel, FileFolderModel, FileModel, IdName } from '@shared/models'; import { LicenseModel } from '@shared/models/license.model'; export interface PreprintStepperStateModel { selectedProviderId: StringOrNull; - preprint: AsyncStateModel; + preprint: AsyncStateModel; fileSource: PreprintFileSource; preprintFilesLinks: AsyncStateModel; preprintFile: AsyncStateModel; diff --git a/src/app/features/preprints/store/preprint-stepper/preprint-stepper.state.ts b/src/app/features/preprints/store/preprint-stepper/preprint-stepper.state.ts index 35cb73caf..78962f57d 100644 --- a/src/app/features/preprints/store/preprint-stepper/preprint-stepper.state.ts +++ b/src/app/features/preprints/store/preprint-stepper/preprint-stepper.state.ts @@ -8,7 +8,7 @@ import { HttpEventType } from '@angular/common/http'; import { inject, Injectable } from '@angular/core'; import { PreprintFileSource } from '@osf/features/preprints/enums'; -import { Preprint } from '@osf/features/preprints/models'; +import { PreprintModel } from '@osf/features/preprints/models'; import { PreprintFilesService, PreprintLicensesService, @@ -198,7 +198,7 @@ export class PreprintStepperState { ctx.setState(patch({ preprint: patch({ isSubmitting: true }) })); return this.preprintFilesService.updateFileRelationship(state.preprint.data!.id, action.fileId).pipe( - tap((preprint: Preprint) => { + tap((preprint: PreprintModel) => { ctx.patchState({ preprint: { ...ctx.getState().preprint, @@ -344,7 +344,7 @@ export class PreprintStepperState { const fileIdAfterCopy = file.id.split('/')[1]; return this.preprintFilesService.updateFileRelationship(createdPreprintId, fileIdAfterCopy).pipe( - tap((preprint: Preprint) => { + tap((preprint: PreprintModel) => { ctx.patchState({ preprint: { ...ctx.getState().preprint, diff --git a/src/app/features/preprints/store/preprint/preprint.actions.ts b/src/app/features/preprints/store/preprint/preprint.actions.ts index 5446d1966..e13179b78 100644 --- a/src/app/features/preprints/store/preprint/preprint.actions.ts +++ b/src/app/features/preprints/store/preprint/preprint.actions.ts @@ -1,15 +1,4 @@ import { StringOrNull } from '@shared/helpers'; -import { SearchFilters } from '@shared/models'; - -export class FetchMyPreprints { - static readonly type = '[Preprint] Fetch My Preprints'; - - constructor( - public pageNumber: number, - public pageSize: number, - public filters: SearchFilters - ) {} -} export class FetchPreprintById { static readonly type = '[Preprint] Fetch Preprint By Id'; @@ -43,6 +32,10 @@ export class FetchPreprintRequestActions { constructor(public requestId: string) {} } +export class FetchPreprintMetrics { + static readonly type = '[Preprint] Fetch Preprint Metrics'; +} + export class WithdrawPreprint { static readonly type = '[Preprint] Withdraw Preprint'; diff --git a/src/app/features/preprints/store/preprint/preprint.model.ts b/src/app/features/preprints/store/preprint/preprint.model.ts index 0f18743c5..6876edc7e 100644 --- a/src/app/features/preprints/store/preprint/preprint.model.ts +++ b/src/app/features/preprints/store/preprint/preprint.model.ts @@ -1,17 +1,17 @@ import { ReviewAction } from '@osf/features/moderation/models'; -import { Preprint, PreprintRequestAction, PreprintShortInfo } from '@osf/features/preprints/models'; -import { PreprintRequest } from '@osf/features/preprints/models/preprint-request.models'; -import { AsyncStateModel, AsyncStateWithTotalCount, FileModel, FileVersionModel } from '@shared/models'; +import { AsyncStateModel, FileModel, FileVersionModel } from '@shared/models'; + +import { PreprintMetrics, PreprintModel, PreprintRequest, PreprintRequestAction } from '../../models'; export interface PreprintStateModel { - myPreprints: AsyncStateWithTotalCount; - preprint: AsyncStateModel; + preprint: AsyncStateModel; preprintFile: AsyncStateModel; fileVersions: AsyncStateModel; preprintVersionIds: AsyncStateModel; preprintReviewActions: AsyncStateModel; preprintRequests: AsyncStateModel; preprintRequestsActions: AsyncStateModel; + metrics: AsyncStateModel; } export const DefaultState: PreprintStateModel = { @@ -21,11 +21,11 @@ export const DefaultState: PreprintStateModel = { error: null, isSubmitting: false, }, - myPreprints: { - data: [], + metrics: { + data: null, isLoading: false, error: null, - totalCount: 0, + isSubmitting: false, }, preprintFile: { data: null, diff --git a/src/app/features/preprints/store/preprint/preprint.selectors.ts b/src/app/features/preprints/store/preprint/preprint.selectors.ts index fa413fd7a..66cdf22fe 100644 --- a/src/app/features/preprints/store/preprint/preprint.selectors.ts +++ b/src/app/features/preprints/store/preprint/preprint.selectors.ts @@ -1,24 +1,11 @@ import { Selector } from '@ngxs/store'; +import { UserPermissions } from '@osf/shared/enums'; + import { PreprintStateModel } from './preprint.model'; import { PreprintState } from './preprint.state'; export class PreprintSelectors { - @Selector([PreprintState]) - static getMyPreprints(state: PreprintStateModel) { - return state.myPreprints.data; - } - - @Selector([PreprintState]) - static getMyPreprintsTotalCount(state: PreprintStateModel) { - return state.myPreprints.totalCount; - } - - @Selector([PreprintState]) - static areMyPreprintsLoading(state: PreprintStateModel) { - return state.myPreprints.isLoading; - } - @Selector([PreprintState]) static getPreprint(state: PreprintStateModel) { return state.preprint.data; @@ -93,4 +80,24 @@ export class PreprintSelectors { static arePreprintRequestActionsLoading(state: PreprintStateModel) { return state.preprintRequestsActions.isLoading; } + + @Selector([PreprintState]) + static hasAdminAccess(state: PreprintStateModel) { + return state.preprint.data?.currentUserPermissions.includes(UserPermissions.Admin) || false; + } + + @Selector([PreprintState]) + static hasWriteAccess(state: PreprintStateModel) { + return state.preprint.data?.currentUserPermissions.includes(UserPermissions.Write) || false; + } + + @Selector([PreprintState]) + static getPreprintMetrics(state: PreprintStateModel) { + return state.metrics.data; + } + + @Selector([PreprintState]) + static arePreprintMetricsLoading(state: PreprintStateModel) { + return state.metrics.isLoading; + } } diff --git a/src/app/features/preprints/store/preprint/preprint.state.ts b/src/app/features/preprints/store/preprint/preprint.state.ts index 4e7a27651..ecda51613 100644 --- a/src/app/features/preprints/store/preprint/preprint.state.ts +++ b/src/app/features/preprints/store/preprint/preprint.state.ts @@ -6,15 +6,16 @@ import { catchError } from 'rxjs/operators'; import { inject, Injectable } from '@angular/core'; -import { PreprintsService } from '@osf/features/preprints/services'; -import { handleSectionError } from '@shared/helpers'; -import { FilesService } from '@shared/services'; +import { handleSectionError } from '@osf/shared/helpers'; +import { FilesService } from '@osf/shared/services'; + +import { PreprintsService } from '../../services'; import { - FetchMyPreprints, FetchPreprintById, FetchPreprintFile, FetchPreprintFileVersions, + FetchPreprintMetrics, FetchPreprintRequestActions, FetchPreprintRequests, FetchPreprintReviewActions, @@ -35,26 +36,6 @@ export class PreprintState { private preprintsService = inject(PreprintsService); private fileService = inject(FilesService); - @Action(FetchMyPreprints) - fetchMyPreprints(ctx: StateContext, action: FetchMyPreprints) { - ctx.setState(patch({ myPreprints: patch({ isLoading: true }) })); - - return this.preprintsService.getMyPreprints(action.pageNumber, action.pageSize, action.filters).pipe( - tap((preprints) => { - ctx.setState( - patch({ - myPreprints: patch({ - isLoading: false, - data: preprints.data, - totalCount: preprints.totalCount, - }), - }) - ); - }), - catchError((error) => handleSectionError(ctx, 'myPreprints', error)) - ); - } - @Action(FetchPreprintById) fetchPreprintById(ctx: StateContext, action: FetchPreprintById) { ctx.setState( @@ -65,16 +46,20 @@ export class PreprintState { preprintReviewActions: patch({ isLoading: false, data: [] }), preprintRequests: patch({ isLoading: false, data: [] }), preprintRequestsActions: patch({ isLoading: false, data: [] }), + metrics: patch({ isLoading: false, data: null }), }) ); return this.preprintsService.getByIdWithEmbeds(action.id).pipe( tap((preprint) => { ctx.setState(patch({ preprint: patch({ isLoading: false, data: preprint }) })); + if (!preprint.dateWithdrawn) { ctx.dispatch(new FetchPreprintFile()); } + ctx.dispatch(new FetchPreprintVersionIds()); + ctx.dispatch(new FetchPreprintMetrics()); }), catchError((error) => handleSectionError(ctx, 'preprint', error)) ); @@ -125,6 +110,22 @@ export class PreprintState { ); } + @Action(FetchPreprintMetrics) + fetchPreprintMetrics(ctx: StateContext) { + const preprintId = ctx.getState().preprint.data?.id; + + if (!preprintId) return; + + ctx.setState(patch({ metrics: patch({ isLoading: true }) })); + + return this.preprintsService.getPreprintMetrics(preprintId).pipe( + tap((metrics) => { + ctx.setState(patch({ metrics: patch({ isLoading: false, data: metrics }) })); + }), + catchError((error) => handleSectionError(ctx, 'metrics', error)) + ); + } + @Action(FetchPreprintReviewActions) fetchPreprintReviewActions(ctx: StateContext) { const preprintId = ctx.getState().preprint.data?.id; diff --git a/src/app/features/profile/components/profile-information/profile-information.component.html b/src/app/features/profile/components/profile-information/profile-information.component.html index d9b6f8aa6..e21756c3f 100644 --- a/src/app/features/profile/components/profile-information/profile-information.component.html +++ b/src/app/features/profile/components/profile-information/profile-information.component.html @@ -21,8 +21,8 @@

{{ currentUser()?.fullName }}

-
-
+
+
@if (currentUser()?.social?.orcid) {
orcid @@ -39,7 +39,7 @@

{{ currentUser()?.fullName }}

@if (isEmploymentAndEducationVisible()) { -
+
@if (currentUser()?.education?.length) {
@@ -51,7 +51,6 @@

@if (currentUser()?.employment?.length) {
- cos-shield

{{ currentUser()?.employment?.[0]?.institution }}

diff --git a/src/app/features/profile/components/profile-information/profile-information.component.spec.ts b/src/app/features/profile/components/profile-information/profile-information.component.spec.ts index c1ca8d324..eb0415ab0 100644 --- a/src/app/features/profile/components/profile-information/profile-information.component.spec.ts +++ b/src/app/features/profile/components/profile-information/profile-information.component.spec.ts @@ -156,15 +156,6 @@ describe('ProfileInformationComponent', () => { expect(socials).toEqual([]); }); - it('should not include profileWebsites in social links', () => { - fixture.componentRef.setInput('currentUser', mockUser); - fixture.detectChanges(); - - const socials = component.userSocials(); - const websites = socials.filter((s) => s.alt === 'settings.profileSettings.social.labels.profileWebsites'); - expect(websites.length).toBe(0); - }); - it('should emit editProfile event when called', (done) => { component.editProfile.subscribe(() => { expect(true).toBe(true); diff --git a/src/app/features/profile/helpers/user-socials.helper.ts b/src/app/features/profile/helpers/user-socials.helper.ts index e4fd979f1..65b739f42 100644 --- a/src/app/features/profile/helpers/user-socials.helper.ts +++ b/src/app/features/profile/helpers/user-socials.helper.ts @@ -26,7 +26,7 @@ export function mapUserSocials( url = social.address + value; } - if (url && social.key !== 'profileWebsites') { + if (url) { acc.push({ url, icon: `assets/icons/socials/${social.icon}`, diff --git a/src/app/features/project/linked-services/linked-services.component.spec.ts b/src/app/features/project/linked-services/linked-services.component.spec.ts index 909cb5837..011fc0289 100644 --- a/src/app/features/project/linked-services/linked-services.component.spec.ts +++ b/src/app/features/project/linked-services/linked-services.component.spec.ts @@ -5,7 +5,7 @@ import { ActivatedRoute } from '@angular/router'; import { UserSelectors } from '@core/store/user'; import { LoadingSpinnerComponent, SubHeaderComponent } from '@osf/shared/components'; -import { AddonsSelectors } from '@shared/stores'; +import { AddonsSelectors } from '@shared/stores/addons'; import { CurrentResourceSelectors } from '@shared/stores/current-resource'; import { LinkedServicesComponent } from './linked-services.component'; diff --git a/src/app/features/project/linked-services/linked-services.component.ts b/src/app/features/project/linked-services/linked-services.component.ts index ff1e5834f..aea781e17 100644 --- a/src/app/features/project/linked-services/linked-services.component.ts +++ b/src/app/features/project/linked-services/linked-services.component.ts @@ -11,7 +11,7 @@ import { UserSelectors } from '@core/store/user'; import { LoadingSpinnerComponent, SubHeaderComponent } from '@shared/components'; import { AddonServiceNames } from '@shared/enums'; import { convertCamelCaseToNormal } from '@shared/helpers'; -import { AddonsSelectors, GetAddonsResourceReference, GetConfiguredLinkAddons } from '@shared/stores'; +import { AddonsSelectors, GetAddonsResourceReference, GetConfiguredLinkAddons } from '@shared/stores/addons'; import { CurrentResourceSelectors } from '@shared/stores/current-resource'; @Component({ diff --git a/src/app/features/project/overview/components/add-component-dialog/add-component-dialog.component.ts b/src/app/features/project/overview/components/add-component-dialog/add-component-dialog.component.ts index 2e603a2d5..a996a4cfe 100644 --- a/src/app/features/project/overview/components/add-component-dialog/add-component-dialog.component.ts +++ b/src/app/features/project/overview/components/add-component-dialog/add-component-dialog.component.ts @@ -19,8 +19,8 @@ import { ComponentFormControls } from '@osf/shared/enums'; import { CustomValidators } from '@osf/shared/helpers'; import { ComponentForm, Institution } from '@osf/shared/models'; import { ToastService } from '@osf/shared/services'; -import { FetchRegions, RegionsSelectors } from '@osf/shared/stores'; import { FetchUserInstitutions, InstitutionsSelectors } from '@osf/shared/stores/institutions'; +import { FetchRegions, RegionsSelectors } from '@osf/shared/stores/regions'; import { CreateComponent, GetComponents, ProjectOverviewSelectors } from '../../store'; diff --git a/src/app/features/project/overview/components/citation-addon-card/citation-addon-card.component.ts b/src/app/features/project/overview/components/citation-addon-card/citation-addon-card.component.ts index e0c9ac1bc..d8e56fdf6 100644 --- a/src/app/features/project/overview/components/citation-addon-card/citation-addon-card.component.ts +++ b/src/app/features/project/overview/components/citation-addon-card/citation-addon-card.component.ts @@ -25,8 +25,8 @@ import { OperationNames, StorageItemType } from '@shared/enums'; import { formatCitation, getItemUrl } from '@shared/helpers'; import { CitationStyle, ConfiguredAddonModel, CustomOption, StorageItem } from '@shared/models'; import { AddonOperationInvocationService, CslStyleManagerService } from '@shared/services'; -import { CitationsSelectors, GetCitationStyles } from '@shared/stores'; import { AddonsSelectors, CreateCitationAddonOperationInvocation } from '@shared/stores/addons'; +import { CitationsSelectors, GetCitationStyles } from '@shared/stores/citations'; import '@citation-js/plugin-csl'; diff --git a/src/app/features/project/overview/components/delete-component-dialog/delete-component-dialog.component.ts b/src/app/features/project/overview/components/delete-component-dialog/delete-component-dialog.component.ts index 85a36db14..fcbd08125 100644 --- a/src/app/features/project/overview/components/delete-component-dialog/delete-component-dialog.component.ts +++ b/src/app/features/project/overview/components/delete-component-dialog/delete-component-dialog.component.ts @@ -16,7 +16,7 @@ import { RegistryOverviewSelectors } from '@osf/features/registry/store/registry import { ScientistsNames } from '@osf/shared/constants'; import { ResourceType, UserPermissions } from '@osf/shared/enums'; import { ToastService } from '@osf/shared/services'; -import { CurrentResourceSelectors } from '@osf/shared/stores'; +import { CurrentResourceSelectors } from '@osf/shared/stores/current-resource'; import { GetComponents, ProjectOverviewSelectors } from '../../store'; diff --git a/src/app/features/project/overview/components/delete-node-link-dialog/delete-node-link-dialog.component.ts b/src/app/features/project/overview/components/delete-node-link-dialog/delete-node-link-dialog.component.ts index 97d9f20f3..78311fe83 100644 --- a/src/app/features/project/overview/components/delete-node-link-dialog/delete-node-link-dialog.component.ts +++ b/src/app/features/project/overview/components/delete-node-link-dialog/delete-node-link-dialog.component.ts @@ -8,7 +8,7 @@ import { DynamicDialogConfig, DynamicDialogRef } from 'primeng/dynamicdialog'; import { ChangeDetectionStrategy, Component, DestroyRef, inject } from '@angular/core'; import { ToastService } from '@osf/shared/services'; -import { DeleteNodeLink, GetLinkedResources, NodeLinksSelectors } from '@osf/shared/stores'; +import { DeleteNodeLink, GetLinkedResources, NodeLinksSelectors } from '@osf/shared/stores/node-links'; import { ProjectOverviewSelectors } from '../../store'; diff --git a/src/app/features/project/overview/components/link-resource-dialog/link-resource-dialog.component.ts b/src/app/features/project/overview/components/link-resource-dialog/link-resource-dialog.component.ts index 0f2058ec1..d0625131a 100644 --- a/src/app/features/project/overview/components/link-resource-dialog/link-resource-dialog.component.ts +++ b/src/app/features/project/overview/components/link-resource-dialog/link-resource-dialog.component.ts @@ -28,15 +28,8 @@ import { FormControl, FormsModule } from '@angular/forms'; import { SearchInputComponent } from '@osf/shared/components'; import { ResourceSearchMode, ResourceType } from '@osf/shared/enums'; import { MyResourcesItem, MyResourcesSearchFilters } from '@osf/shared/models'; -import { - CreateNodeLink, - DeleteNodeLink, - GetLinkedResources, - GetMyProjects, - GetMyRegistrations, - MyResourcesSelectors, - NodeLinksSelectors, -} from '@osf/shared/stores'; +import { GetMyProjects, GetMyRegistrations, MyResourcesSelectors } from '@osf/shared/stores/my-resources'; +import { CreateNodeLink, DeleteNodeLink, GetLinkedResources, NodeLinksSelectors } from '@osf/shared/stores/node-links'; import { ProjectOverviewSelectors } from '../../store'; diff --git a/src/app/features/project/overview/components/linked-resources/linked-resources.component.ts b/src/app/features/project/overview/components/linked-resources/linked-resources.component.ts index 9a163e821..503e1f984 100644 --- a/src/app/features/project/overview/components/linked-resources/linked-resources.component.ts +++ b/src/app/features/project/overview/components/linked-resources/linked-resources.component.ts @@ -9,7 +9,7 @@ import { ChangeDetectionStrategy, Component, inject, input } from '@angular/core import { ContributorsListComponent, IconComponent, TruncatedTextComponent } from '@osf/shared/components'; import { CustomDialogService } from '@osf/shared/services'; -import { NodeLinksSelectors } from '@osf/shared/stores'; +import { NodeLinksSelectors } from '@osf/shared/stores/node-links'; import { DeleteNodeLinkDialogComponent } from '../delete-node-link-dialog/delete-node-link-dialog.component'; import { LinkResourceDialogComponent } from '../link-resource-dialog/link-resource-dialog.component'; diff --git a/src/app/features/project/overview/components/overview-collections/overview-collections.component.ts b/src/app/features/project/overview/components/overview-collections/overview-collections.component.ts index b85f48c30..addec8fa1 100644 --- a/src/app/features/project/overview/components/overview-collections/overview-collections.component.ts +++ b/src/app/features/project/overview/components/overview-collections/overview-collections.component.ts @@ -14,7 +14,7 @@ import { collectionFilterNames } from '@osf/features/collections/constants'; import { SubmissionReviewStatus } from '@osf/features/moderation/enums'; import { StopPropagationDirective } from '@osf/shared/directives'; import { CollectionSubmission, ResourceOverview } from '@osf/shared/models'; -import { CollectionsSelectors, GetProjectSubmissions } from '@osf/shared/stores'; +import { CollectionsSelectors, GetProjectSubmissions } from '@osf/shared/stores/collections'; @Component({ selector: 'osf-overview-collections', diff --git a/src/app/features/project/overview/components/overview-components/overview-components.component.ts b/src/app/features/project/overview/components/overview-components/overview-components.component.ts index e66832c11..3f08be57b 100644 --- a/src/app/features/project/overview/components/overview-components/overview-components.component.ts +++ b/src/app/features/project/overview/components/overview-components/overview-components.component.ts @@ -12,7 +12,7 @@ import { Router } from '@angular/router'; import { ContributorsListComponent, IconComponent, TruncatedTextComponent } from '@osf/shared/components'; import { ResourceType, UserPermissions } from '@osf/shared/enums'; import { CustomDialogService, LoaderService } from '@osf/shared/services'; -import { GetResourceWithChildren } from '@osf/shared/stores'; +import { GetResourceWithChildren } from '@osf/shared/stores/current-resource'; import { ComponentOverview } from '@shared/models'; import { LoadMoreComponents, ProjectOverviewSelectors } from '../../store'; diff --git a/src/app/features/project/overview/components/overview-toolbar/overview-toolbar.component.ts b/src/app/features/project/overview/components/overview-toolbar/overview-toolbar.component.ts index 876e1c046..907acd44f 100644 --- a/src/app/features/project/overview/components/overview-toolbar/overview-toolbar.component.ts +++ b/src/app/features/project/overview/components/overview-toolbar/overview-toolbar.component.ts @@ -22,13 +22,8 @@ import { ResourceType } from '@osf/shared/enums'; import { ToolbarResource } from '@osf/shared/models'; import { FileSizePipe } from '@osf/shared/pipes'; import { CustomDialogService, ToastService } from '@osf/shared/services'; -import { - AddResourceToBookmarks, - BookmarksSelectors, - GetMyBookmarks, - MyResourcesSelectors, - RemoveResourceFromBookmarks, -} from '@osf/shared/stores'; +import { AddResourceToBookmarks, BookmarksSelectors, RemoveResourceFromBookmarks } from '@osf/shared/stores/bookmarks'; +import { GetMyBookmarks, MyResourcesSelectors } from '@osf/shared/stores/my-resources'; import { hasViewOnlyParam } from '@shared/helpers'; import { DuplicateDialogComponent } from '../duplicate-dialog/duplicate-dialog.component'; diff --git a/src/app/features/project/overview/components/overview-wiki/overview-wiki.component.ts b/src/app/features/project/overview/components/overview-wiki/overview-wiki.component.ts index 979f1992f..ee38257ec 100644 --- a/src/app/features/project/overview/components/overview-wiki/overview-wiki.component.ts +++ b/src/app/features/project/overview/components/overview-wiki/overview-wiki.component.ts @@ -9,7 +9,7 @@ import { ChangeDetectionStrategy, Component, computed, inject, input } from '@an import { Router } from '@angular/router'; import { MarkdownComponent, TruncatedTextComponent } from '@osf/shared/components'; -import { WikiSelectors } from '@osf/shared/stores'; +import { WikiSelectors } from '@osf/shared/stores/wiki'; @Component({ selector: 'osf-overview-wiki', diff --git a/src/app/features/project/overview/components/toggle-publicity-dialog/toggle-publicity-dialog.component.ts b/src/app/features/project/overview/components/toggle-publicity-dialog/toggle-publicity-dialog.component.ts index ad83ae1d8..615be3309 100644 --- a/src/app/features/project/overview/components/toggle-publicity-dialog/toggle-publicity-dialog.component.ts +++ b/src/app/features/project/overview/components/toggle-publicity-dialog/toggle-publicity-dialog.component.ts @@ -21,7 +21,7 @@ import { ComponentsSelectionListComponent, LoadingSpinnerComponent } from '@osf/ import { UserPermissions } from '@osf/shared/enums'; import { ComponentCheckboxItemModel } from '@osf/shared/models'; import { ToastService } from '@osf/shared/services'; -import { CurrentResourceSelectors } from '@osf/shared/stores'; +import { CurrentResourceSelectors } from '@osf/shared/stores/current-resource'; import { TogglePublicityStep } from '../../enums'; import { PrivacyStatusModel } from '../../models'; diff --git a/src/app/features/project/overview/project-overview.component.html b/src/app/features/project/overview/project-overview.component.html index f9d5a7850..f65b70d76 100644 --- a/src/app/features/project/overview/project-overview.component.html +++ b/src/app/features/project/overview/project-overview.component.html @@ -89,6 +89,10 @@ [isCollectionsRoute]="isCollectionsRoute()" [canEdit]="hasAdminAccess()" [showEditButton]="hasWriteAccess()" + [bibliographicContributors]="bibliographicContributors()" + [isBibliographicContributorsLoading]="isBibliographicContributorsLoading()" + [hasMoreBibliographicContributors]="hasMoreBibliographicContributors()" + (loadMoreContributors)="handleLoadMoreContributors()" />

diff --git a/src/app/features/project/overview/project-overview.component.ts b/src/app/features/project/overview/project-overview.component.ts index 8c87fc8e0..291cb4254 100644 --- a/src/app/features/project/overview/project-overview.component.ts +++ b/src/app/features/project/overview/project-overview.component.ts @@ -33,25 +33,26 @@ import { Mode, ResourceType } from '@osf/shared/enums'; import { hasViewOnlyParam } from '@osf/shared/helpers'; import { MapProjectOverview } from '@osf/shared/mappers'; import { CustomDialogService, MetaTagsService, ToastService } from '@osf/shared/services'; +import { GetActivityLogs } from '@osf/shared/stores/activity-logs'; import { AddonsSelectors, - ClearCollections, ClearConfiguredAddons, - ClearWiki, - CollectionsSelectors, - CurrentResourceSelectors, - FetchSelectedSubjects, GetAddonsResourceReference, - GetBookmarksCollectionId, - GetCollectionProvider, GetConfiguredCitationAddons, GetConfiguredStorageAddons, - GetHomeWiki, - GetLinkedResources, - GetResourceWithChildren, - SubjectsSelectors, -} from '@osf/shared/stores'; -import { GetActivityLogs } from '@osf/shared/stores/activity-logs'; +} from '@osf/shared/stores/addons'; +import { GetBookmarksCollectionId } from '@osf/shared/stores/bookmarks'; +import { ClearCollections, CollectionsSelectors, GetCollectionProvider } from '@osf/shared/stores/collections'; +import { + ContributorsSelectors, + GetBibliographicContributors, + LoadMoreBibliographicContributors, + ResetContributorsState, +} from '@osf/shared/stores/contributors'; +import { CurrentResourceSelectors, GetResourceWithChildren } from '@osf/shared/stores/current-resource'; +import { GetLinkedResources } from '@osf/shared/stores/node-links'; +import { FetchSelectedSubjects, SubjectsSelectors } from '@osf/shared/stores/subjects'; +import { ClearWiki, GetHomeWiki } from '@osf/shared/stores/wiki'; import { LoadingSpinnerComponent, MakeDecisionDialogComponent, @@ -139,6 +140,9 @@ export class ProjectOverviewComponent implements OnInit { isWikiEnabled = select(ProjectOverviewSelectors.isWikiEnabled); parentProject = select(ProjectOverviewSelectors.getParentProject); isParentProjectLoading = select(ProjectOverviewSelectors.getParentProjectLoading); + bibliographicContributors = select(ContributorsSelectors.getBibliographicContributors); + isBibliographicContributorsLoading = select(ContributorsSelectors.isBibliographicContributorsLoading); + hasMoreBibliographicContributors = select(ContributorsSelectors.hasMoreBibliographicContributors); addonsResourceReference = select(AddonsSelectors.getAddonsResourceReference); configuredCitationAddons = select(AddonsSelectors.getConfiguredCitationAddons); operationInvocation = select(AddonsSelectors.getOperationInvocation); @@ -164,6 +168,9 @@ export class ProjectOverviewComponent implements OnInit { getParentProject: GetParentProject, getAddonsResourceReference: GetAddonsResourceReference, getConfiguredCitationAddons: GetConfiguredCitationAddons, + getBibliographicContributors: GetBibliographicContributors, + loadMoreBibliographicContributors: LoadMoreBibliographicContributors, + resetContributorsState: ResetContributorsState, }); readonly activityPageSize = 5; @@ -193,8 +200,9 @@ export class ProjectOverviewComponent implements OnInit { resourceOverview = computed(() => { const project = this.currentProject(); const subjects = this.subjects(); + const bibliographicContributors = this.bibliographicContributors(); if (project) { - return MapProjectOverview(project, subjects, this.isAnonymous()); + return MapProjectOverview(project, subjects, this.isAnonymous(), bibliographicContributors); } return null; }); @@ -282,6 +290,10 @@ export class ProjectOverviewComponent implements OnInit { this.actions.setProjectCustomCitation(citation); } + handleLoadMoreContributors(): void { + this.actions.loadMoreBibliographicContributors(this.currentProject()?.id, ResourceType.Project); + } + ngOnInit(): void { const projectId = this.route.snapshot.params['id'] || this.route.parent?.snapshot.params['id']; @@ -291,6 +303,7 @@ export class ProjectOverviewComponent implements OnInit { this.actions.getComponents(projectId); this.actions.getLinkedProjects(projectId); this.actions.getActivityLogs(projectId, this.activityDefaultPage, this.activityPageSize); + this.actions.getBibliographicContributors(projectId, ResourceType.Project); } this.dataciteService @@ -385,6 +398,7 @@ export class ProjectOverviewComponent implements OnInit { this.actions.getComponents(projectId); this.actions.getLinkedProjects(projectId); this.actions.getActivityLogs(projectId, this.activityDefaultPage, this.activityPageSize); + this.actions.getBibliographicContributors(projectId, ResourceType.Project); }), takeUntilDestroyed(this.destroyRef) ) @@ -398,6 +412,7 @@ export class ProjectOverviewComponent implements OnInit { this.actions.clearCollections(); this.actions.clearCollectionModeration(); this.actions.clearConfiguredAddons(); + this.actions.resetContributorsState(); }); } diff --git a/src/app/features/project/overview/services/project-overview.service.ts b/src/app/features/project/overview/services/project-overview.service.ts index 4261b22a8..eb8bf543a 100644 --- a/src/app/features/project/overview/services/project-overview.service.ts +++ b/src/app/features/project/overview/services/project-overview.service.ts @@ -34,14 +34,7 @@ export class ProjectOverviewService { getProjectById(projectId: string): Observable { const params: Record = { - 'embed[]': [ - 'bibliographic_contributors', - 'affiliated_institutions', - 'identifiers', - 'license', - 'storage', - 'preprints', - ], + 'embed[]': ['affiliated_institutions', 'identifiers', 'license', 'storage', 'preprints'], 'fields[institutions]': 'assets,description,name', 'fields[preprints]': 'title,date_created', 'fields[users]': 'family_name,full_name,given_name,middle_name', diff --git a/src/app/features/project/project-addons/components/configure-addon/configure-addon.component.spec.ts b/src/app/features/project/project-addons/components/configure-addon/configure-addon.component.spec.ts index 0dae909d5..182e23e10 100644 --- a/src/app/features/project/project-addons/components/configure-addon/configure-addon.component.spec.ts +++ b/src/app/features/project/project-addons/components/configure-addon/configure-addon.component.spec.ts @@ -8,7 +8,7 @@ import { HttpTestingController } from '@angular/common/http/testing'; import { ComponentFixture, inject, TestBed } from '@angular/core/testing'; import { ActivatedRoute, Router } from '@angular/router'; -import { AddonsState } from '@osf/shared/stores'; +import { AddonsState } from '@osf/shared/stores/addons'; import { ConfigureAddonComponent } from './configure-addon.component'; diff --git a/src/app/features/project/project-addons/components/configure-addon/configure-addon.component.ts b/src/app/features/project/project-addons/components/configure-addon/configure-addon.component.ts index bda7efa20..20a3b1cce 100644 --- a/src/app/features/project/project-addons/components/configure-addon/configure-addon.component.ts +++ b/src/app/features/project/project-addons/components/configure-addon/configure-addon.component.ts @@ -33,7 +33,7 @@ import { CreateAddonOperationInvocation, GetLinkAddons, UpdateConfiguredAddon, -} from '@osf/shared/stores'; +} from '@osf/shared/stores/addons'; import { AddonDialogService } from '../../services/addon-dialog.service'; diff --git a/src/app/features/project/project-addons/components/confirm-account-connection-modal/confirm-account-connection-modal.component.ts b/src/app/features/project/project-addons/components/confirm-account-connection-modal/confirm-account-connection-modal.component.ts index 5c098c638..8ea7fda4b 100644 --- a/src/app/features/project/project-addons/components/confirm-account-connection-modal/confirm-account-connection-modal.component.ts +++ b/src/app/features/project/project-addons/components/confirm-account-connection-modal/confirm-account-connection-modal.component.ts @@ -9,7 +9,7 @@ import { ChangeDetectionStrategy, Component, inject } from '@angular/core'; import { ReactiveFormsModule } from '@angular/forms'; import { AddonOperationInvocationService } from '@osf/shared/services'; -import { AddonsSelectors, CreateAddonOperationInvocation } from '@osf/shared/stores'; +import { AddonsSelectors, CreateAddonOperationInvocation } from '@osf/shared/stores/addons'; import { OperationNames } from '@shared/enums'; @Component({ diff --git a/src/app/features/project/project-addons/components/connect-configured-addon/connect-configured-addon.component.ts b/src/app/features/project/project-addons/components/connect-configured-addon/connect-configured-addon.component.ts index 1d9c77d8d..05d1575ee 100644 --- a/src/app/features/project/project-addons/components/connect-configured-addon/connect-configured-addon.component.ts +++ b/src/app/features/project/project-addons/components/connect-configured-addon/connect-configured-addon.component.ts @@ -39,7 +39,7 @@ import { GetAuthorizedStorageAddons, UpdateAuthorizedAddon, UpdateConfiguredAddon, -} from '@osf/shared/stores'; +} from '@osf/shared/stores/addons'; import { AddonConfigMap } from '../../models'; import { AddonDialogService } from '../../services'; diff --git a/src/app/features/project/project-addons/components/disconnect-addon-modal/disconnect-addon-modal.component.ts b/src/app/features/project/project-addons/components/disconnect-addon-modal/disconnect-addon-modal.component.ts index 4991e4aea..56ed5e320 100644 --- a/src/app/features/project/project-addons/components/disconnect-addon-modal/disconnect-addon-modal.component.ts +++ b/src/app/features/project/project-addons/components/disconnect-addon-modal/disconnect-addon-modal.component.ts @@ -9,7 +9,7 @@ import { ChangeDetectionStrategy, Component, computed, inject } from '@angular/c import { AddonType } from '@osf/shared/enums'; import { getAddonTypeString } from '@osf/shared/helpers'; -import { AddonsSelectors, DeleteConfiguredAddon } from '@osf/shared/stores'; +import { AddonsSelectors, DeleteConfiguredAddon } from '@osf/shared/stores/addons'; @Component({ selector: 'osf-disconnect-addon-modal', @@ -31,9 +31,8 @@ export class DisconnectAddonModalComponent { ? 'settings.addons.configureAddon.linkedItem' : 'settings.addons.configureAddon.selectedFolder'; }); - actions = createDispatchMap({ - deleteConfiguredAddon: DeleteConfiguredAddon, - }); + + actions = createDispatchMap({ deleteConfiguredAddon: DeleteConfiguredAddon }); handleDisconnectAddonAccount(): void { if (!this.addon) return; diff --git a/src/app/features/project/project-addons/project-addons.component.spec.ts b/src/app/features/project/project-addons/project-addons.component.spec.ts index c29ff66bf..e4f6b109b 100644 --- a/src/app/features/project/project-addons/project-addons.component.spec.ts +++ b/src/app/features/project/project-addons/project-addons.component.spec.ts @@ -4,7 +4,7 @@ import { signal } from '@angular/core'; import { ComponentFixture, TestBed } from '@angular/core/testing'; import { UserSelectors, UserState } from '@core/store/user'; -import { AddonsState } from '@osf/shared/stores'; +import { AddonsState } from '@osf/shared/stores/addons'; import { ProjectAddonsComponent } from './project-addons.component'; diff --git a/src/app/features/project/project.routes.ts b/src/app/features/project/project.routes.ts index 5d7d3bae8..a6af0de78 100644 --- a/src/app/features/project/project.routes.ts +++ b/src/app/features/project/project.routes.ts @@ -5,16 +5,13 @@ import { Routes } from '@angular/router'; import { viewOnlyGuard } from '@osf/core/guards'; import { ResourceType } from '@osf/shared/enums'; import { LicensesService } from '@osf/shared/services'; -import { - CitationsState, - CollectionsState, - ContributorsState, - DuplicatesState, - NodeLinksState, - SubjectsState, - ViewOnlyLinkState, -} from '@osf/shared/stores'; import { ActivityLogsState } from '@osf/shared/stores/activity-logs'; +import { CitationsState } from '@osf/shared/stores/citations'; +import { CollectionsState } from '@osf/shared/stores/collections'; +import { DuplicatesState } from '@osf/shared/stores/duplicates'; +import { NodeLinksState } from '@osf/shared/stores/node-links'; +import { SubjectsState } from '@osf/shared/stores/subjects'; +import { ViewOnlyLinkState } from '@osf/shared/stores/view-only-links'; import { AnalyticsState } from '../analytics/store'; import { CollectionsModerationState } from '../moderation/store/collections-moderation'; @@ -53,7 +50,7 @@ export const projectRoutes: Routes = [ { path: 'metadata', loadChildren: () => import('@osf/features/metadata/metadata.routes').then((mod) => mod.metadataRoutes), - providers: [provideStates([SubjectsState, ContributorsState])], + providers: [provideStates([SubjectsState])], data: { resourceType: ResourceType.Project }, canActivate: [viewOnlyGuard], }, @@ -87,7 +84,7 @@ export const projectRoutes: Routes = [ canActivate: [viewOnlyGuard], loadComponent: () => import('../contributors/contributors.component').then((mod) => mod.ContributorsComponent), data: { resourceType: ResourceType.Project }, - providers: [provideStates([ContributorsState, ViewOnlyLinkState])], + providers: [provideStates([ViewOnlyLinkState])], }, { path: 'analytics', diff --git a/src/app/features/project/registrations/registrations.component.ts b/src/app/features/project/registrations/registrations.component.ts index 8ac6abb1c..bf0102113 100644 --- a/src/app/features/project/registrations/registrations.component.ts +++ b/src/app/features/project/registrations/registrations.component.ts @@ -18,7 +18,7 @@ import { RegistrationCardComponent, SubHeaderComponent, } from '@osf/shared/components'; -import { CurrentResourceSelectors } from '@shared/stores'; +import { CurrentResourceSelectors } from '@shared/stores/current-resource'; import { GetRegistrations, RegistrationsSelectors } from './store'; diff --git a/src/app/features/project/settings/components/delete-project-dialog/delete-project-dialog.component.ts b/src/app/features/project/settings/components/delete-project-dialog/delete-project-dialog.component.ts index 1a6a7713c..39cdb738e 100644 --- a/src/app/features/project/settings/components/delete-project-dialog/delete-project-dialog.component.ts +++ b/src/app/features/project/settings/components/delete-project-dialog/delete-project-dialog.component.ts @@ -15,7 +15,7 @@ import { Router } from '@angular/router'; import { ScientistsNames } from '@osf/shared/constants'; import { UserPermissions } from '@osf/shared/enums'; import { ToastService } from '@osf/shared/services'; -import { CurrentResourceSelectors } from '@osf/shared/stores'; +import { CurrentResourceSelectors } from '@osf/shared/stores/current-resource'; import { DeleteProject, SettingsSelectors } from '../../store'; diff --git a/src/app/features/project/settings/components/settings-project-affiliation/settings-project-affiliation.component.spec.ts b/src/app/features/project/settings/components/settings-project-affiliation/settings-project-affiliation.component.spec.ts index 74290b667..85f9371a3 100644 --- a/src/app/features/project/settings/components/settings-project-affiliation/settings-project-affiliation.component.spec.ts +++ b/src/app/features/project/settings/components/settings-project-affiliation/settings-project-affiliation.component.spec.ts @@ -1,7 +1,7 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; import { Institution } from '@osf/shared/models'; -import { InstitutionsSelectors } from '@osf/shared/stores'; +import { InstitutionsSelectors } from '@osf/shared/stores/institutions'; import { SettingsProjectAffiliationComponent } from './settings-project-affiliation.component'; diff --git a/src/app/features/project/settings/components/settings-project-affiliation/settings-project-affiliation.component.ts b/src/app/features/project/settings/components/settings-project-affiliation/settings-project-affiliation.component.ts index 6d55ef188..9e764d439 100644 --- a/src/app/features/project/settings/components/settings-project-affiliation/settings-project-affiliation.component.ts +++ b/src/app/features/project/settings/components/settings-project-affiliation/settings-project-affiliation.component.ts @@ -9,7 +9,7 @@ import { NgOptimizedImage } from '@angular/common'; import { ChangeDetectionStrategy, Component, computed, input, OnInit, output } from '@angular/core'; import { Institution } from '@osf/shared/models'; -import { FetchUserInstitutions, InstitutionsSelectors } from '@shared/stores'; +import { FetchUserInstitutions, InstitutionsSelectors } from '@shared/stores/institutions'; @Component({ selector: 'osf-settings-project-affiliation', diff --git a/src/app/features/project/settings/settings.component.spec.ts b/src/app/features/project/settings/settings.component.spec.ts index 18458936b..ed275c037 100644 --- a/src/app/features/project/settings/settings.component.spec.ts +++ b/src/app/features/project/settings/settings.component.spec.ts @@ -14,7 +14,7 @@ import { } from '@osf/features/project/settings/components'; import { LoadingSpinnerComponent, SubHeaderComponent } from '@osf/shared/components'; import { CustomConfirmationService, LoaderService, ToastService } from '@osf/shared/services'; -import { ViewOnlyLinkSelectors } from '@osf/shared/stores'; +import { ViewOnlyLinkSelectors } from '@osf/shared/stores/view-only-links'; import { SettingsComponent } from './settings.component'; import { SettingsSelectors } from './store'; diff --git a/src/app/features/project/settings/settings.component.ts b/src/app/features/project/settings/settings.component.ts index 3d00d1697..51cba11f5 100644 --- a/src/app/features/project/settings/settings.component.ts +++ b/src/app/features/project/settings/settings.component.ts @@ -14,13 +14,8 @@ import { LoadingSpinnerComponent, SubHeaderComponent } from '@osf/shared/compone import { ResourceType, SubscriptionEvent, SubscriptionFrequency } from '@osf/shared/enums'; import { Institution, UpdateNodeRequestModel, ViewOnlyLinkModel } from '@osf/shared/models'; import { CustomConfirmationService, CustomDialogService, LoaderService, ToastService } from '@osf/shared/services'; -import { - DeleteViewOnlyLink, - FetchViewOnlyLinks, - GetResource, - GetResourceWithChildren, - ViewOnlyLinkSelectors, -} from '@osf/shared/stores'; +import { GetResource, GetResourceWithChildren } from '@osf/shared/stores/current-resource'; +import { DeleteViewOnlyLink, FetchViewOnlyLinks, ViewOnlyLinkSelectors } from '@osf/shared/stores/view-only-links'; import { DeleteProjectDialogComponent, diff --git a/src/app/features/project/wiki/legacy-wiki-redirect.component.ts b/src/app/features/project/wiki/legacy-wiki-redirect.component.ts index 090ccb30e..fc4515624 100644 --- a/src/app/features/project/wiki/legacy-wiki-redirect.component.ts +++ b/src/app/features/project/wiki/legacy-wiki-redirect.component.ts @@ -8,7 +8,7 @@ import { ActivatedRoute, Router } from '@angular/router'; import { ResourceType } from '@osf/shared/enums'; import { LoaderService } from '@osf/shared/services'; -import { GetWikiList, WikiSelectors } from '@osf/shared/stores'; +import { GetWikiList, WikiSelectors } from '@osf/shared/stores/wiki'; @Component({ template: '', diff --git a/src/app/features/project/wiki/wiki.component.spec.ts b/src/app/features/project/wiki/wiki.component.spec.ts index a6a823274..a9f76c326 100644 --- a/src/app/features/project/wiki/wiki.component.spec.ts +++ b/src/app/features/project/wiki/wiki.component.spec.ts @@ -19,7 +19,7 @@ import { ViewSectionComponent, WikiListComponent, } from '@shared/components/wiki'; -import { WikiState } from '@shared/stores'; +import { WikiState } from '@shared/stores/wiki'; describe('WikiComponent', () => { let component: WikiComponent; diff --git a/src/app/features/project/wiki/wiki.component.ts b/src/app/features/project/wiki/wiki.component.ts index 7a324ed22..38eedf314 100644 --- a/src/app/features/project/wiki/wiki.component.ts +++ b/src/app/features/project/wiki/wiki.component.ts @@ -22,11 +22,11 @@ import { ResourceType } from '@osf/shared/enums'; import { hasViewOnlyParam } from '@osf/shared/helpers'; import { WikiModes } from '@osf/shared/models'; import { ToastService } from '@osf/shared/services'; +import { CurrentResourceSelectors } from '@osf/shared/stores/current-resource'; import { ClearWiki, CreateWiki, CreateWikiVersion, - CurrentResourceSelectors, DeleteWiki, GetCompareVersionContent, GetComponentsWikiList, @@ -39,7 +39,7 @@ import { ToggleMode, UpdateWikiPreviewContent, WikiSelectors, -} from '@osf/shared/stores'; +} from '@osf/shared/stores/wiki'; import { ViewOnlyLinkMessageComponent } from '@shared/components/view-only-link-message/view-only-link-message.component'; @Component({ diff --git a/src/app/features/registries/components/custom-step/custom-step.component.html b/src/app/features/registries/components/custom-step/custom-step.component.html index a8cdbc290..d8e434a69 100644 --- a/src/app/features/registries/components/custom-step/custom-step.component.html +++ b/src/app/features/registries/components/custom-step/custom-step.component.html @@ -30,7 +30,7 @@

{{ section.title }}

@if (section.description) { -

{{ section.description }}

+

{{ section.description }}

} @@ -40,151 +40,150 @@

{{ section.title }}

@for (q of questions; track q.id) {
- - - @if (q.exampleText) { - - - {{ 'common.links.showExample' | translate }} - - - - {{ 'common.links.hideExample' | translate }} - -

{{ q.exampleText }}

-
-
+ - @switch (q.fieldType) { - @case (FieldType.TextArea) { - - @if ( - stepForm.controls[q.responseKey!].errors?.['required'] && - (stepForm.controls[q.responseKey!].touched || stepForm.controls[q.responseKey!].dirty) - ) { - - {{ INPUT_VALIDATION_MESSAGES.required | translate }} - - } - } - @case (FieldType.Radio) { -
- @for (option of q.options; track option) { -
- - - - @if (option.helpText) { - - } -
- } -
- @if ( - stepForm.controls[q.responseKey!].errors?.['required'] && - (stepForm.controls[q.responseKey!].touched || stepForm.controls[q.responseKey!].dirty) - ) { - - {{ INPUT_VALIDATION_MESSAGES.required | translate }} - - } + @if (q.exampleText) { + + + {{ 'common.links.showExample' | translate }} + + + + {{ 'common.links.hideExample' | translate }} + +

{{ q.exampleText }}

+
+
+ } + + @switch (q.fieldType) { + @case (FieldType.TextArea) { + + @if ( + stepForm.controls[q.responseKey!].errors?.['required'] && + (stepForm.controls[q.responseKey!].touched || stepForm.controls[q.responseKey!].dirty) + ) { + + {{ INPUT_VALIDATION_MESSAGES.required | translate }} + } - @case (FieldType.Checkbox) { -
- @for (option of q.options; track option) { -
- - - - @if (option.helpText) { - - } -
- } -
- @if ( - stepForm.controls[q.responseKey!].errors?.['required'] && - (stepForm.controls[q.responseKey!].touched || stepForm.controls[q.responseKey!].dirty) - ) { - - {{ INPUT_VALIDATION_MESSAGES.required | translate }} - + } + @case (FieldType.Radio) { +
+ @for (option of q.options; track option) { +
+ + + + @if (option.helpText) { + + } +
} +
+ @if ( + stepForm.controls[q.responseKey!].errors?.['required'] && + (stepForm.controls[q.responseKey!].touched || stepForm.controls[q.responseKey!].dirty) + ) { + + {{ INPUT_VALIDATION_MESSAGES.required | translate }} + } + } + @case (FieldType.Checkbox) { +
+ @for (option of q.options; track option) { +
+ - @case (FieldType.Text) { - - @if ( - stepForm.controls[q.responseKey!].errors?.['required'] && - (stepForm.controls[q.responseKey!].touched || stepForm.controls[q.responseKey!].dirty) - ) { - - {{ INPUT_VALIDATION_MESSAGES.required | translate }} - + + @if (option.helpText) { + + } +
} +
+ @if ( + stepForm.controls[q.responseKey!].errors?.['required'] && + (stepForm.controls[q.responseKey!].touched || stepForm.controls[q.responseKey!].dirty) + ) { + + {{ INPUT_VALIDATION_MESSAGES.required | translate }} + } - @case (FieldType.File) { -

{{ 'files.actions.uploadFile' | translate }}

-

- {{ 'shared.files.limitText' | translate }} -

-

- {{ 'shared.files.description' | translate }} -

-
- @for (file of attachedFiles[q.responseKey!] || []; track file) { - - } -
-
- -
+ } + + @case (FieldType.Text) { + + @if ( + stepForm.controls[q.responseKey!].errors?.['required'] && + (stepForm.controls[q.responseKey!].touched || stepForm.controls[q.responseKey!].dirty) + ) { + + {{ INPUT_VALIDATION_MESSAGES.required | translate }} + } } -
+ @case (FieldType.File) { +

{{ 'files.actions.uploadFile' | translate }}

+

+ {{ 'shared.files.limitText' | translate }} +

+

+ {{ 'shared.files.description' | translate }} +

+
+ @for (file of attachedFiles[q.responseKey!] || []; track file) { + + } +
+
+ +
+ } + }
}
diff --git a/src/app/features/registries/components/drafts/drafts.component.spec.ts b/src/app/features/registries/components/drafts/drafts.component.spec.ts index cda932900..a38d7d65c 100644 --- a/src/app/features/registries/components/drafts/drafts.component.spec.ts +++ b/src/app/features/registries/components/drafts/drafts.component.spec.ts @@ -7,7 +7,8 @@ import { ActivatedRoute, NavigationEnd, Router } from '@angular/router'; import { RegistriesSelectors } from '@osf/features/registries/store'; import { StepperComponent, SubHeaderComponent } from '@osf/shared/components'; -import { ContributorsSelectors, SubjectsSelectors } from '@shared/stores'; +import { ContributorsSelectors } from '@shared/stores/contributors'; +import { SubjectsSelectors } from '@shared/stores/subjects'; import { DraftsComponent } from './drafts.component'; diff --git a/src/app/features/registries/components/drafts/drafts.component.ts b/src/app/features/registries/components/drafts/drafts.component.ts index 4c4c4c04f..ad9c6262c 100644 --- a/src/app/features/registries/components/drafts/drafts.component.ts +++ b/src/app/features/registries/components/drafts/drafts.component.ts @@ -22,12 +22,8 @@ import { StepperComponent, SubHeaderComponent } from '@osf/shared/components'; import { ResourceType } from '@osf/shared/enums'; import { PageSchema, Question, StepOption } from '@osf/shared/models'; import { LoaderService } from '@osf/shared/services'; -import { - ContributorsSelectors, - FetchSelectedSubjects, - GetAllContributors, - SubjectsSelectors, -} from '@osf/shared/stores'; +import { ContributorsSelectors, GetAllContributors } from '@osf/shared/stores/contributors'; +import { FetchSelectedSubjects, SubjectsSelectors } from '@osf/shared/stores/subjects'; import { DEFAULT_STEPS } from '../../constants'; import { ClearState, FetchDraft, FetchSchemaBlocks, RegistriesSelectors, UpdateStepState } from '../../store'; diff --git a/src/app/features/registries/components/justification-review/justification-review.component.html b/src/app/features/registries/components/justification-review/justification-review.component.html index 5f8c45cda..480466477 100644 --- a/src/app/features/registries/components/justification-review/justification-review.component.html +++ b/src/app/features/registries/components/justification-review/justification-review.component.html @@ -1,7 +1,8 @@ -
+

{{ 'registries.justification.step' | translate }}

{{ 'registries.justification.title' | translate }}

+ @if (schemaResponse()?.revisionJustification) {

{{ schemaResponse()?.revisionJustification }}

} @else { @@ -12,7 +13,9 @@

{{ 'registries.justification.title' | translate }}

} +

{{ 'registries.justification.updatedList' | translate }}

+ @if (changes().length) { @for (item of changes(); track item) {

{{ item }}

@@ -21,6 +24,7 @@

{{ 'registries.justification.updatedList' | translate }}

{{ 'registries.justification.noUpdates' | translate }}

} + @for (page of pages(); track page.id) {

{{ page.title }}

@@ -28,6 +32,14 @@

{{ page.title }}

@if (page.description) {

{{ page.description }}

} + + @if (page.questions?.length) { + + } + @if (page.sections?.length) { @for (section of page.sections; track section.id) {
@@ -43,51 +55,39 @@

{{ section.title }}

}
} - } @else { - @if (page.questions?.length) { - - } }
} -
-
- @if (inProgress) { - - - - - } @else if (isUnapproved) { - - - } -
+ +
+ @if (inProgress) { + + + + + + + } @else if (isUnapproved) { + + + + }
diff --git a/src/app/features/registries/components/registries-metadata-step/registries-affiliated-institution/registries-affiliated-institution.component.spec.ts b/src/app/features/registries/components/registries-metadata-step/registries-affiliated-institution/registries-affiliated-institution.component.spec.ts index ef6845138..1ca5f56ba 100644 --- a/src/app/features/registries/components/registries-metadata-step/registries-affiliated-institution/registries-affiliated-institution.component.spec.ts +++ b/src/app/features/registries/components/registries-metadata-step/registries-affiliated-institution/registries-affiliated-institution.component.spec.ts @@ -1,7 +1,7 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; import { ActivatedRoute } from '@angular/router'; -import { InstitutionsSelectors } from '@osf/shared/stores'; +import { InstitutionsSelectors } from '@osf/shared/stores/institutions'; import { RegistriesAffiliatedInstitutionComponent } from './registries-affiliated-institution.component'; diff --git a/src/app/features/registries/components/registries-metadata-step/registries-affiliated-institution/registries-affiliated-institution.component.ts b/src/app/features/registries/components/registries-metadata-step/registries-affiliated-institution/registries-affiliated-institution.component.ts index 1a6ffdad8..261eceddb 100644 --- a/src/app/features/registries/components/registries-metadata-step/registries-affiliated-institution/registries-affiliated-institution.component.ts +++ b/src/app/features/registries/components/registries-metadata-step/registries-affiliated-institution/registries-affiliated-institution.component.ts @@ -15,7 +15,7 @@ import { FetchUserInstitutions, InstitutionsSelectors, UpdateResourceInstitutions, -} from '@osf/shared/stores'; +} from '@osf/shared/stores/institutions'; @Component({ selector: 'osf-registries-affiliated-institution', diff --git a/src/app/features/registries/components/registries-metadata-step/registries-contributors/registries-contributors.component.html b/src/app/features/registries/components/registries-metadata-step/registries-contributors/registries-contributors.component.html index 7726dae69..70164d25d 100644 --- a/src/app/features/registries/components/registries-metadata-step/registries-contributors/registries-contributors.component.html +++ b/src/app/features/registries/components/registries-metadata-step/registries-contributors/registries-contributors.component.html @@ -6,8 +6,9 @@

{{ 'project.overview.metadata.contributors' | translate }}

[(contributors)]="contributors" [tableParams]="tableParams()" [isLoading]="isContributorsLoading()" + [isLoadingMore]="isLoadingMore()" (remove)="removeContributor($event)" - (pageChanged)="pageChanged($event)" + (loadMore)="loadMoreContributors()" />
diff --git a/src/app/features/registries/components/registries-metadata-step/registries-contributors/registries-contributors.component.spec.ts b/src/app/features/registries/components/registries-metadata-step/registries-contributors/registries-contributors.component.spec.ts index 35d007b66..bbef5fbd3 100644 --- a/src/app/features/registries/components/registries-metadata-step/registries-contributors/registries-contributors.component.spec.ts +++ b/src/app/features/registries/components/registries-metadata-step/registries-contributors/registries-contributors.component.spec.ts @@ -9,8 +9,8 @@ import { ActivatedRoute } from '@angular/router'; import { UserSelectors } from '@core/store/user'; import { ResourceType } from '@osf/shared/enums'; import { CustomConfirmationService, CustomDialogService, ToastService } from '@osf/shared/services'; -import { ContributorsSelectors } from '@osf/shared/stores'; -import { ContributorsTableComponent } from '@shared/components/contributors'; +import { ContributorsSelectors } from '@osf/shared/stores/contributors/contributors.selectors'; +import { ContributorsTableComponent } from '@shared/components/contributors/contributors-table/contributors-table.component'; import { RegistriesContributorsComponent } from './registries-contributors.component'; @@ -77,15 +77,12 @@ describe('RegistriesContributorsComponent', () => { deleteContributor: jest.fn().mockReturnValue(of({})), bulkUpdateContributors: jest.fn().mockReturnValue(of({})), bulkAddContributors: jest.fn().mockReturnValue(of({})), + resetContributorsState: jest.fn().mockRejectedValue(of({})), } as any; Object.defineProperty(component, 'actions', { value: mockActions }); fixture.detectChanges(); }); - it('should create', () => { - expect(component).toBeTruthy(); - }); - it('should request contributors on init', () => { const actions = (component as any).actions; expect(actions.getContributors).toHaveBeenCalledWith('draft-1', ResourceType.DraftRegistration); diff --git a/src/app/features/registries/components/registries-metadata-step/registries-contributors/registries-contributors.component.ts b/src/app/features/registries/components/registries-metadata-step/registries-contributors/registries-contributors.component.ts index bf07f0974..1a8a71f66 100644 --- a/src/app/features/registries/components/registries-metadata-step/registries-contributors/registries-contributors.component.ts +++ b/src/app/features/registries/components/registries-metadata-step/registries-contributors/registries-contributors.component.ts @@ -4,7 +4,7 @@ import { TranslatePipe } from '@ngx-translate/core'; import { Button } from 'primeng/button'; import { Card } from 'primeng/card'; -import { TableModule, TablePageEvent } from 'primeng/table'; +import { TableModule } from 'primeng/table'; import { filter, map, of } from 'rxjs'; @@ -16,6 +16,7 @@ import { effect, inject, input, + OnDestroy, OnInit, signal, } from '@angular/core'; @@ -40,7 +41,9 @@ import { ContributorsSelectors, DeleteContributor, GetAllContributors, -} from '@osf/shared/stores'; + LoadMoreContributors, + ResetContributorsState, +} from '@osf/shared/stores/contributors'; @Component({ selector: 'osf-registries-contributors', @@ -49,7 +52,7 @@ import { styleUrl: './registries-contributors.component.scss', changeDetection: ChangeDetectionStrategy.OnPush, }) -export class RegistriesContributorsComponent implements OnInit { +export class RegistriesContributorsComponent implements OnInit, OnDestroy { control = input.required(); readonly destroyRef = inject(DestroyRef); @@ -65,14 +68,15 @@ export class RegistriesContributorsComponent implements OnInit { isContributorsLoading = select(ContributorsSelectors.isContributorsLoading); contributorsTotalCount = select(ContributorsSelectors.getContributorsTotalCount); - page = select(ContributorsSelectors.getContributorsPageNumber); + isLoadingMore = select(ContributorsSelectors.isContributorsLoadingMore); pageSize = select(ContributorsSelectors.getContributorsPageSize); readonly tableParams = computed(() => ({ ...DEFAULT_TABLE_PARAMS, totalRecords: this.contributorsTotalCount(), - paginator: this.contributorsTotalCount() > DEFAULT_TABLE_PARAMS.rows, - firstRowIndex: (this.page() - 1) * this.pageSize(), + paginator: false, + scrollable: true, + firstRowIndex: 0, rows: this.pageSize(), })); @@ -82,6 +86,8 @@ export class RegistriesContributorsComponent implements OnInit { bulkUpdateContributors: BulkUpdateContributors, bulkAddContributors: BulkAddContributors, addContributor: AddContributor, + loadMoreContributors: LoadMoreContributors, + resetContributorsState: ResetContributorsState, }); get hasChanges(): boolean { @@ -98,6 +104,10 @@ export class RegistriesContributorsComponent implements OnInit { this.actions.getContributors(this.draftId(), ResourceType.DraftRegistration); } + ngOnDestroy(): void { + this.actions.resetContributorsState(); + } + onFocusOut() { if (this.control()) { this.control().markAsTouched(); @@ -122,13 +132,10 @@ export class RegistriesContributorsComponent implements OnInit { } openAddContributorDialog() { - const addedContributorIds = this.initialContributors().map((x) => x.userId); - this.customDialogService .open(AddContributorDialogComponent, { header: 'project.contributors.addDialog.addRegisteredContributor', width: '448px', - data: addedContributorIds, }) .onClose.pipe( filter((res: ContributorDialogAddModel) => !!res), @@ -192,10 +199,7 @@ export class RegistriesContributorsComponent implements OnInit { }); } - pageChanged(event: TablePageEvent) { - const page = Math.floor(event.first / event.rows) + 1; - const pageSize = event.rows; - - this.actions.getContributors(this.draftId(), ResourceType.DraftRegistration, page, pageSize); + loadMoreContributors(): void { + this.actions.loadMoreContributors(this.draftId(), ResourceType.DraftRegistration); } } diff --git a/src/app/features/registries/components/registries-metadata-step/registries-metadata-step.component.spec.ts b/src/app/features/registries/components/registries-metadata-step/registries-metadata-step.component.spec.ts index 02fc8dfe6..cac750345 100644 --- a/src/app/features/registries/components/registries-metadata-step/registries-metadata-step.component.spec.ts +++ b/src/app/features/registries/components/registries-metadata-step/registries-metadata-step.component.spec.ts @@ -3,15 +3,18 @@ import { MockComponents, MockProvider } from 'ng-mocks'; import { ComponentFixture, TestBed } from '@angular/core/testing'; import { ActivatedRoute, Router } from '@angular/router'; -import { RegistriesContributorsComponent } from '@osf/features/registries/components/registries-metadata-step/registries-contributors/registries-contributors.component'; -import { RegistriesLicenseComponent } from '@osf/features/registries/components/registries-metadata-step/registries-license/registries-license.component'; -import { RegistriesSubjectsComponent } from '@osf/features/registries/components/registries-metadata-step/registries-subjects/registries-subjects.component'; -import { RegistriesTagsComponent } from '@osf/features/registries/components/registries-metadata-step/registries-tags/registries-tags.component'; -import { RegistriesSelectors } from '@osf/features/registries/store'; import { CustomConfirmationService } from '@osf/shared/services'; -import { ContributorsSelectors, InstitutionsSelectors, SubjectsSelectors } from '@osf/shared/stores'; +import { ContributorsSelectors } from '@osf/shared/stores/contributors'; +import { InstitutionsSelectors } from '@osf/shared/stores/institutions'; +import { SubjectsSelectors } from '@osf/shared/stores/subjects'; import { TextInputComponent } from '@shared/components'; +import { RegistriesSelectors } from '../../store'; + +import { RegistriesContributorsComponent } from './registries-contributors/registries-contributors.component'; +import { RegistriesLicenseComponent } from './registries-license/registries-license.component'; +import { RegistriesSubjectsComponent } from './registries-subjects/registries-subjects.component'; +import { RegistriesTagsComponent } from './registries-tags/registries-tags.component'; import { RegistriesMetadataStepComponent } from './registries-metadata-step.component'; import { OSFTestingModule } from '@testing/osf.testing.module'; diff --git a/src/app/features/registries/components/registries-metadata-step/registries-metadata-step.component.ts b/src/app/features/registries/components/registries-metadata-step/registries-metadata-step.component.ts index 977b252c9..2ca52b2ab 100644 --- a/src/app/features/registries/components/registries-metadata-step/registries-metadata-step.component.ts +++ b/src/app/features/registries/components/registries-metadata-step/registries-metadata-step.component.ts @@ -18,7 +18,8 @@ import { INPUT_VALIDATION_MESSAGES, InputLimits } from '@osf/shared/constants'; import { CustomValidators, findChangedFields } from '@osf/shared/helpers'; import { ContributorModel, DraftRegistrationModel, SubjectModel } from '@osf/shared/models'; import { CustomConfirmationService } from '@osf/shared/services'; -import { ContributorsSelectors, SubjectsSelectors } from '@osf/shared/stores'; +import { ContributorsSelectors } from '@osf/shared/stores/contributors'; +import { SubjectsSelectors } from '@osf/shared/stores/subjects'; import { UserPermissions } from '@shared/enums'; import { ClearState, DeleteDraft, RegistriesSelectors, UpdateDraft, UpdateStepState } from '../../store'; diff --git a/src/app/features/registries/components/registries-metadata-step/registries-subjects/registries-subjects.component.spec.ts b/src/app/features/registries/components/registries-metadata-step/registries-subjects/registries-subjects.component.spec.ts index 4102b431a..822320ac4 100644 --- a/src/app/features/registries/components/registries-metadata-step/registries-subjects/registries-subjects.component.spec.ts +++ b/src/app/features/registries/components/registries-metadata-step/registries-subjects/registries-subjects.component.spec.ts @@ -8,7 +8,7 @@ import { ActivatedRoute } from '@angular/router'; import { RegistriesSelectors } from '@osf/features/registries/store'; import { ResourceType } from '@osf/shared/enums'; -import { SubjectsSelectors } from '@osf/shared/stores'; +import { SubjectsSelectors } from '@osf/shared/stores/subjects'; import { RegistriesSubjectsComponent } from './registries-subjects.component'; diff --git a/src/app/features/registries/components/registries-metadata-step/registries-subjects/registries-subjects.component.ts b/src/app/features/registries/components/registries-metadata-step/registries-subjects/registries-subjects.component.ts index ea6db6907..e7ad85599 100644 --- a/src/app/features/registries/components/registries-metadata-step/registries-subjects/registries-subjects.component.ts +++ b/src/app/features/registries/components/registries-metadata-step/registries-subjects/registries-subjects.component.ts @@ -20,7 +20,7 @@ import { FetchSubjects, SubjectsSelectors, UpdateResourceSubjects, -} from '@osf/shared/stores'; +} from '@osf/shared/stores/subjects'; @Component({ selector: 'osf-registries-subjects', diff --git a/src/app/features/registries/components/review/review.component.html b/src/app/features/registries/components/review/review.component.html index 691afff6c..46cfb88b6 100644 --- a/src/app/features/registries/components/review/review.component.html +++ b/src/app/features/registries/components/review/review.component.html @@ -13,6 +13,7 @@

{{ 'common.labels.title' | translate }}

}
+

{{ 'common.labels.description' | translate }}

{{ draftRegistration()?.description }}

@@ -27,22 +28,19 @@

{{ 'common.labels.description' | translate }}

{{ 'common.labels.contributors' | translate }}

- +
-

{{ 'shared.license.title' | translate }}

+

{{ 'common.labels.license' | translate }}

+ @if (draftRegistration()?.license && license()) { - - - -
{{ license()?.name }}
-
- -
{{ license()!.text | interpolate: licenseOptionsRecord() }}
-
-
-
+ } @else {

{{ 'common.labels.noData' | translate }}

@@ -50,6 +48,7 @@

{{ 'shared.license.title' | translate }}

}
+

{{ 'shared.subjects.title' | translate }}

@@ -57,6 +56,7 @@

{{ 'shared.subjects.title' | translate }}

}
+ @if (!subjects().length) {

{{ 'common.labels.noData' | translate }}

@@ -77,6 +77,7 @@

{{ 'shared.tags.title' | translate }}

}
+ @for (page of pages(); track page.id) {

{{ page.title }}

@@ -85,6 +86,14 @@

{{ page.title }}

{{ page.description }}

} + @if (page.questions?.length) { + + } + @if (page.sections?.length) { @for (section of page.sections; track section.id) {
@@ -101,14 +110,6 @@

{{ section.title }}

}
} - } @else { - @if (page.questions?.length) { - - } }
} diff --git a/src/app/features/registries/components/review/review.component.spec.ts b/src/app/features/registries/components/review/review.component.spec.ts index 705ff9be9..2b9752afd 100644 --- a/src/app/features/registries/components/review/review.component.spec.ts +++ b/src/app/features/registries/components/review/review.component.spec.ts @@ -14,7 +14,8 @@ import { } from '@osf/shared/components'; import { FieldType } from '@osf/shared/enums'; import { CustomConfirmationService, CustomDialogService, ToastService } from '@osf/shared/services'; -import { ContributorsSelectors, SubjectsSelectors } from '@osf/shared/stores'; +import { ContributorsSelectors } from '@osf/shared/stores/contributors'; +import { SubjectsSelectors } from '@osf/shared/stores/subjects'; import { ReviewComponent } from './review.component'; diff --git a/src/app/features/registries/components/review/review.component.ts b/src/app/features/registries/components/review/review.component.ts index cc8b2e29a..3a58230e7 100644 --- a/src/app/features/registries/components/review/review.component.ts +++ b/src/app/features/registries/components/review/review.component.ts @@ -2,7 +2,6 @@ import { createDispatchMap, select } from '@ngxs/store'; import { TranslatePipe } from '@ngx-translate/core'; -import { Accordion, AccordionContent, AccordionHeader, AccordionPanel } from 'primeng/accordion'; import { Button } from 'primeng/button'; import { Card } from 'primeng/card'; import { Message } from 'primeng/message'; @@ -10,22 +9,26 @@ import { Tag } from 'primeng/tag'; import { map, of } from 'rxjs'; -import { ChangeDetectionStrategy, Component, computed, effect, inject } from '@angular/core'; +import { ChangeDetectionStrategy, Component, computed, effect, inject, OnDestroy } from '@angular/core'; import { toSignal } from '@angular/core/rxjs-interop'; import { ActivatedRoute, Router } from '@angular/router'; import { ENVIRONMENT } from '@core/provider/environment.provider'; -import { ContributorsListComponent, RegistrationBlocksDataComponent } from '@osf/shared/components'; +import { + ContributorsListComponent, + LicenseDisplayComponent, + RegistrationBlocksDataComponent, +} from '@osf/shared/components'; import { INPUT_VALIDATION_MESSAGES } from '@osf/shared/constants'; import { FieldType, ResourceType, UserPermissions } from '@osf/shared/enums'; -import { InterpolatePipe } from '@osf/shared/pipes'; import { CustomConfirmationService, CustomDialogService, ToastService } from '@osf/shared/services'; import { ContributorsSelectors, - FetchSelectedSubjects, GetAllContributors, - SubjectsSelectors, -} from '@osf/shared/stores'; + LoadMoreContributors, + ResetContributorsState, +} from '@osf/shared/stores/contributors'; +import { FetchSelectedSubjects, SubjectsSelectors } from '@osf/shared/stores/subjects'; import { ClearState, @@ -46,19 +49,15 @@ import { SelectComponentsDialogComponent } from '../select-components-dialog/sel Message, Tag, Button, - Accordion, - AccordionContent, - AccordionHeader, - AccordionPanel, - InterpolatePipe, RegistrationBlocksDataComponent, ContributorsListComponent, + LicenseDisplayComponent, ], templateUrl: './review.component.html', styleUrl: './review.component.scss', changeDetection: ChangeDetectionStrategy.OnPush, }) -export class ReviewComponent { +export class ReviewComponent implements OnDestroy { private readonly router = inject(Router); private readonly route = inject(ActivatedRoute); private readonly customConfirmationService = inject(CustomConfirmationService); @@ -73,6 +72,8 @@ export class ReviewComponent { readonly stepsData = select(RegistriesSelectors.getStepsData); readonly INPUT_VALIDATION_MESSAGES = INPUT_VALIDATION_MESSAGES; readonly contributors = select(ContributorsSelectors.getContributors); + readonly areContributorsLoading = select(ContributorsSelectors.isContributorsLoading); + readonly hasMoreContributors = select(ContributorsSelectors.hasMoreContributors); readonly subjects = select(SubjectsSelectors.getSelectedSubjects); readonly components = select(RegistriesSelectors.getRegistrationComponents); readonly license = select(RegistriesSelectors.getRegistrationLicense); @@ -88,6 +89,8 @@ export class ReviewComponent { getProjectsComponents: FetchProjectChildren, fetchLicenses: FetchLicenses, updateStepState: UpdateStepState, + loadMoreContributors: LoadMoreContributors, + resetContributorsState: ResetContributorsState, }); private readonly draftId = toSignal(this.route.params.pipe(map((params) => params['id'])) ?? of(undefined)); @@ -134,6 +137,10 @@ export class ReviewComponent { }); } + ngOnDestroy(): void { + this.actions.resetContributorsState(); + } + goBack(): void { const previousStep = this.pages().length; this.router.navigate(['../', previousStep], { relativeTo: this.route }); @@ -206,4 +213,8 @@ export class ReviewComponent { } }); } + + loadMoreContributors(): void { + this.actions.loadMoreContributors(this.draftId(), ResourceType.DraftRegistration); + } } diff --git a/src/app/features/registries/registries.routes.ts b/src/app/features/registries/registries.routes.ts index 9e5e74892..ecff049e7 100644 --- a/src/app/features/registries/registries.routes.ts +++ b/src/app/features/registries/registries.routes.ts @@ -6,8 +6,10 @@ import { registrationModerationGuard } from '@core/guards/registration-moderatio import { authGuard } from '@osf/core/guards'; import { RegistriesComponent } from '@osf/features/registries/registries.component'; import { RegistriesState } from '@osf/features/registries/store'; -import { CitationsState, ContributorsState, SubjectsState } from '@osf/shared/stores'; +import { CitationsState } from '@osf/shared/stores/citations'; +import { ContributorsState } from '@osf/shared/stores/contributors'; import { RegistrationProviderState } from '@osf/shared/stores/registration-provider'; +import { SubjectsState } from '@osf/shared/stores/subjects'; import { LicensesHandlers, ProjectsHandlers, ProvidersHandlers } from './store/handlers'; import { FilesHandlers } from './store/handlers/files.handlers'; diff --git a/src/app/features/registry/pages/registry-overview/registry-overview.component.html b/src/app/features/registry/pages/registry-overview/registry-overview.component.html index 387f1f2a6..0a0151a42 100644 --- a/src/app/features/registry/pages/registry-overview/registry-overview.component.html +++ b/src/app/features/registry/pages/registry-overview/registry-overview.component.html @@ -93,7 +93,18 @@

{{ page.title }}

@if (page.description) { -

{{ page.description }}

+

{{ page.description }}

+ } + + @if (page.questions?.length) { + } @if (page.sections?.length) { @@ -101,7 +112,7 @@

{{ page.title }}

{{ section.title }}

@if (section.description) { -

{{ section.description }}

+

{{ section.description }}

} @if (section.questions?.length) { {{ section.title }} }
} - } @else { - @if (page.questions?.length) { - - } } }
@@ -136,6 +136,10 @@

{{ section.title }}

(customCitationUpdated)="onCustomCitationUpdated($event)" [canEdit]="hasWriteAccess()" [showEditButton]="hasWriteAccess()" + [bibliographicContributors]="bibliographicContributors()" + [isBibliographicContributorsLoading]="isBibliographicContributorsLoading()" + [hasMoreBibliographicContributors]="hasMoreBibliographicContributors()" + (loadMoreContributors)="handleLoadMoreContributors()" />
diff --git a/src/app/features/registry/pages/registry-overview/registry-overview.component.ts b/src/app/features/registry/pages/registry-overview/registry-overview.component.ts index af1b77fb6..dddd713b2 100644 --- a/src/app/features/registry/pages/registry-overview/registry-overview.component.ts +++ b/src/app/features/registry/pages/registry-overview/registry-overview.component.ts @@ -35,7 +35,13 @@ import { hasViewOnlyParam, toCamelCase } from '@osf/shared/helpers'; import { MapRegistryOverview } from '@osf/shared/mappers'; import { SchemaResponse, ToolbarResource } from '@osf/shared/models'; import { CustomDialogService, ToastService } from '@osf/shared/services'; -import { FetchSelectedSubjects, GetBookmarksCollectionId, SubjectsSelectors } from '@osf/shared/stores'; +import { GetBookmarksCollectionId } from '@osf/shared/stores/bookmarks'; +import { + ContributorsSelectors, + GetBibliographicContributors, + LoadMoreBibliographicContributors, +} from '@osf/shared/stores/contributors'; +import { FetchSelectedSubjects, SubjectsSelectors } from '@osf/shared/stores/subjects'; import { ArchivingMessageComponent, RegistryRevisionsComponent, RegistryStatusesComponent } from '../../components'; import { RegistryMakeDecisionComponent } from '../../components/registry-make-decision/registry-make-decision.component'; @@ -90,6 +96,9 @@ export class RegistryOverviewComponent { readonly areReviewActionsLoading = select(RegistryOverviewSelectors.areReviewActionsLoading); readonly currentRevision = select(RegistriesSelectors.getSchemaResponse); readonly isSchemaResponseLoading = select(RegistriesSelectors.getSchemaResponseLoading); + bibliographicContributors = select(ContributorsSelectors.getBibliographicContributors); + isBibliographicContributorsLoading = select(ContributorsSelectors.isBibliographicContributorsLoading); + hasMoreBibliographicContributors = select(ContributorsSelectors.hasMoreBibliographicContributors); readonly hasWriteAccess = select(RegistryOverviewSelectors.hasWriteAccess); readonly hasAdminAccess = select(RegistryOverviewSelectors.hasAdminAccess); @@ -180,6 +189,8 @@ export class RegistryOverviewComponent { getRegistryReviewActions: GetRegistryReviewActions, getSchemaResponse: FetchAllSchemaResponses, createSchemaResponse: CreateSchemaResponse, + getBibliographicContributors: GetBibliographicContributors, + loadMoreBibliographicContributors: LoadMoreBibliographicContributors, }); revisionId: string | null = null; @@ -213,6 +224,7 @@ export class RegistryOverviewComponent { effect(() => { if (this.registryId()) { this.actions.getRegistryById(this.registryId()); + this.actions.getBibliographicContributors(this.registryId(), ResourceType.Registration); } }); @@ -272,6 +284,10 @@ export class RegistryOverviewComponent { .subscribe(); } + handleLoadMoreContributors(): void { + this.actions.loadMoreBibliographicContributors(this.registry()?.id, ResourceType.Registration); + } + private navigateToJustificationPage(): void { const revisionId = this.revisionId || this.revisionInProgress?.id; this.router.navigate([`/registries/revisions/${revisionId}/justification`]); diff --git a/src/app/features/registry/pages/registry-wiki/registry-wiki.component.spec.ts b/src/app/features/registry/pages/registry-wiki/registry-wiki.component.spec.ts index 8dc9d21a5..7259a5d1f 100644 --- a/src/app/features/registry/pages/registry-wiki/registry-wiki.component.spec.ts +++ b/src/app/features/registry/pages/registry-wiki/registry-wiki.component.spec.ts @@ -7,7 +7,7 @@ import { ActivatedRoute, Router } from '@angular/router'; import { WikiModes } from '@osf/shared/models'; import { SubHeaderComponent, ViewOnlyLinkMessageComponent } from '@shared/components'; import { CompareSectionComponent, ViewSectionComponent, WikiListComponent } from '@shared/components/wiki'; -import { WikiSelectors } from '@shared/stores'; +import { WikiSelectors } from '@shared/stores/wiki'; import { RegistryWikiComponent } from './registry-wiki.component'; diff --git a/src/app/features/registry/pages/registry-wiki/registry-wiki.component.ts b/src/app/features/registry/pages/registry-wiki/registry-wiki.component.ts index f9c35ff7f..f8493272a 100644 --- a/src/app/features/registry/pages/registry-wiki/registry-wiki.component.ts +++ b/src/app/features/registry/pages/registry-wiki/registry-wiki.component.ts @@ -27,7 +27,7 @@ import { SetCurrentWiki, ToggleMode, WikiSelectors, -} from '@osf/shared/stores'; +} from '@osf/shared/stores/wiki'; @Component({ selector: 'osf-registry-wiki', diff --git a/src/app/features/registry/registry.routes.ts b/src/app/features/registry/registry.routes.ts index f26006bbe..31815be3e 100644 --- a/src/app/features/registry/registry.routes.ts +++ b/src/app/features/registry/registry.routes.ts @@ -5,13 +5,10 @@ import { Routes } from '@angular/router'; import { viewOnlyGuard } from '@osf/core/guards'; import { ResourceType } from '@osf/shared/enums'; import { LicensesService } from '@osf/shared/services'; -import { - CitationsState, - ContributorsState, - DuplicatesState, - SubjectsState, - ViewOnlyLinkState, -} from '@osf/shared/stores'; +import { CitationsState } from '@osf/shared/stores/citations'; +import { DuplicatesState } from '@osf/shared/stores/duplicates'; +import { SubjectsState } from '@osf/shared/stores/subjects'; +import { ViewOnlyLinkState } from '@osf/shared/stores/view-only-links'; import { ActivityLogsState } from '@shared/stores/activity-logs'; import { AnalyticsState } from '../analytics/store'; @@ -52,7 +49,7 @@ export const registryRoutes: Routes = [ { path: 'metadata', loadChildren: () => import('@osf/features/metadata/metadata.routes').then((mod) => mod.metadataRoutes), - providers: [provideStates([SubjectsState, ContributorsState])], + providers: [provideStates([SubjectsState])], data: { resourceType: ResourceType.Registration }, canActivate: [viewOnlyGuard], }, @@ -68,7 +65,7 @@ export const registryRoutes: Routes = [ canActivate: [viewOnlyGuard], loadComponent: () => import('../contributors/contributors.component').then((mod) => mod.ContributorsComponent), data: { resourceType: ResourceType.Registration }, - providers: [provideStates([ContributorsState, ViewOnlyLinkState])], + providers: [provideStates([ViewOnlyLinkState])], }, { path: 'analytics', diff --git a/src/app/features/settings/account-settings/account-settings.component.spec.ts b/src/app/features/settings/account-settings/account-settings.component.spec.ts index 6afd881fd..6a8cfd3c9 100644 --- a/src/app/features/settings/account-settings/account-settings.component.spec.ts +++ b/src/app/features/settings/account-settings/account-settings.component.spec.ts @@ -9,6 +9,11 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; import { provideNoopAnimations } from '@angular/platform-browser/animations'; import { UserSelectors } from '@osf/core/store/user'; +import { SubHeaderComponent } from '@osf/shared/components'; +import { ToastService } from '@osf/shared/services'; +import { RegionsSelectors } from '@osf/shared/stores/regions'; + +import { AccountSettingsComponent } from './account-settings.component'; import { AffiliatedInstitutionsComponent, ChangePasswordComponent, @@ -18,13 +23,8 @@ import { DefaultStorageLocationComponent, ShareIndexingComponent, TwoFactorAuthComponent, -} from '@osf/features/settings/account-settings/components'; -import { AccountSettingsSelectors } from '@osf/features/settings/account-settings/store'; -import { SubHeaderComponent } from '@osf/shared/components'; -import { RegionsSelectors } from '@osf/shared/stores'; -import { ToastService } from '@shared/services'; - -import { AccountSettingsComponent } from './account-settings.component'; +} from './components'; +import { AccountSettingsSelectors } from './store'; import { MOCK_STORE, MOCK_USER, MockCustomConfirmationServiceProvider, TranslateServiceMock } from '@testing/mocks'; diff --git a/src/app/features/settings/account-settings/account-settings.component.ts b/src/app/features/settings/account-settings/account-settings.component.ts index 2a0187d38..36882cb61 100644 --- a/src/app/features/settings/account-settings/account-settings.component.ts +++ b/src/app/features/settings/account-settings/account-settings.component.ts @@ -8,7 +8,7 @@ import { ReactiveFormsModule } from '@angular/forms'; import { GetEmails } from '@core/store/user-emails'; import { UserSelectors } from '@osf/core/store/user'; import { SubHeaderComponent } from '@osf/shared/components'; -import { FetchRegions } from '@osf/shared/stores'; +import { FetchRegions } from '@osf/shared/stores/regions'; import { AffiliatedInstitutionsComponent, diff --git a/src/app/features/settings/account-settings/components/default-storage-location/default-storage-location.component.spec.ts b/src/app/features/settings/account-settings/components/default-storage-location/default-storage-location.component.spec.ts index a5a9e723a..072034d2b 100644 --- a/src/app/features/settings/account-settings/components/default-storage-location/default-storage-location.component.spec.ts +++ b/src/app/features/settings/account-settings/components/default-storage-location/default-storage-location.component.spec.ts @@ -9,7 +9,7 @@ import { signal } from '@angular/core'; import { ComponentFixture, TestBed } from '@angular/core/testing'; import { UserSelectors, UserState } from '@osf/core/store/user'; -import { RegionsSelectors, RegionsState } from '@osf/shared/stores'; +import { RegionsSelectors, RegionsState } from '@osf/shared/stores/regions'; import { LoaderService, ToastService } from '@shared/services'; import { AccountSettingsState } from '../../store'; diff --git a/src/app/shared/components/add-project-form/add-project-form.component.spec.ts b/src/app/shared/components/add-project-form/add-project-form.component.spec.ts index 9dfc2e78e..a066932db 100644 --- a/src/app/shared/components/add-project-form/add-project-form.component.spec.ts +++ b/src/app/shared/components/add-project-form/add-project-form.component.spec.ts @@ -11,7 +11,9 @@ import { ProjectFormControls } from '@osf/shared/enums'; import { CustomValidators } from '@osf/shared/helpers'; import { ProjectForm } from '@osf/shared/models'; import { ProjectModel } from '@osf/shared/models/projects'; -import { InstitutionsSelectors, ProjectsSelectors, RegionsSelectors } from '@osf/shared/stores'; +import { InstitutionsSelectors } from '@osf/shared/stores/institutions'; +import { ProjectsSelectors } from '@osf/shared/stores/projects'; +import { RegionsSelectors } from '@osf/shared/stores/regions'; import { AffiliatedInstitutionSelectComponent, ProjectSelectorComponent } from '@shared/components'; import { AddProjectFormComponent } from './add-project-form.component'; diff --git a/src/app/shared/components/add-project-form/add-project-form.component.ts b/src/app/shared/components/add-project-form/add-project-form.component.ts index c7c40f02b..876e92273 100644 --- a/src/app/shared/components/add-project-form/add-project-form.component.ts +++ b/src/app/shared/components/add-project-form/add-project-form.component.ts @@ -16,7 +16,8 @@ import { FormGroup, ReactiveFormsModule } from '@angular/forms'; import { UserSelectors } from '@core/store/user'; import { ProjectFormControls } from '@osf/shared/enums'; import { Institution, ProjectForm, ProjectModel } from '@osf/shared/models'; -import { FetchRegions, FetchUserInstitutions, InstitutionsSelectors, RegionsSelectors } from '@osf/shared/stores'; +import { FetchUserInstitutions, InstitutionsSelectors } from '@osf/shared/stores/institutions'; +import { FetchRegions, RegionsSelectors } from '@osf/shared/stores/regions'; import { AffiliatedInstitutionSelectComponent } from '../affiliated-institution-select/affiliated-institution-select.component'; import { ProjectSelectorComponent } from '../project-selector/project-selector.component'; diff --git a/src/app/shared/components/addons/storage-item-selector/storage-item-selector.component.spec.ts b/src/app/shared/components/addons/storage-item-selector/storage-item-selector.component.spec.ts index 170ab3e57..18b50ae71 100644 --- a/src/app/shared/components/addons/storage-item-selector/storage-item-selector.component.spec.ts +++ b/src/app/shared/components/addons/storage-item-selector/storage-item-selector.component.spec.ts @@ -7,7 +7,7 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; import { GoogleFilePickerComponent, SelectComponent } from '@shared/components'; import { StorageItemSelectorComponent } from '@shared/components/addons'; import { OperationNames } from '@shared/enums'; -import { AddonsSelectors } from '@shared/stores'; +import { AddonsSelectors } from '@shared/stores/addons'; import { OSFTestingModule } from '@testing/osf.testing.module'; import { DialogServiceMockBuilder } from '@testing/providers/dialog-provider.mock'; diff --git a/src/app/shared/components/addons/storage-item-selector/storage-item-selector.component.ts b/src/app/shared/components/addons/storage-item-selector/storage-item-selector.component.ts index 6d36a4dfb..c965e907a 100644 --- a/src/app/shared/components/addons/storage-item-selector/storage-item-selector.component.ts +++ b/src/app/shared/components/addons/storage-item-selector/storage-item-selector.component.ts @@ -30,7 +30,7 @@ import { AddonType, OperationNames, StorageItemType } from '@osf/shared/enums'; import { convertCamelCaseToNormal, IS_XSMALL } from '@osf/shared/helpers'; import { OperationInvokeData, StorageItem } from '@osf/shared/models'; import { CustomDialogService } from '@osf/shared/services'; -import { AddonsSelectors, ClearOperationInvocations } from '@osf/shared/stores'; +import { AddonsSelectors, ClearOperationInvocations } from '@osf/shared/stores/addons'; import { GoogleFilePickerComponent } from '../../google-file-picker/google-file-picker.component'; import { SelectComponent } from '../../select/select.component'; diff --git a/src/app/shared/components/contributors-list/contributors-list.component.html b/src/app/shared/components/contributors-list/contributors-list.component.html index 7c379d360..814ad1dac 100644 --- a/src/app/shared/components/contributors-list/contributors-list.component.html +++ b/src/app/shared/components/contributors-list/contributors-list.component.html @@ -1,17 +1,34 @@ -
+
@if (anonymous()) {

{{ 'project.overview.metadata.anonymousContributors' | translate }}

} @else { - @for (contributor of contributors(); track contributor.id) { -
- @if (readonly() || contributor.isUnregisteredContributor || !contributor.id || contributor.deactivated) { - {{ contributor.fullName }}{{ $last ? '' : ',' }} - } @else { - - {{ contributor.fullName }}{{ $last ? '' : ',' }} - - } + @if (isLoading()) { +
+
+ } @else { + @for (contributor of contributors(); track contributor.id) { +
+ @if (readonly() || contributor.isUnregisteredContributor || !contributor.id || contributor.deactivated) { + {{ contributor.fullName }}{{ $last ? '' : ',' }} + } @else { + + {{ contributor.fullName }}{{ $last ? '' : ',' }} + + } +
+ } } }
+ +@if (hasLoadMore()) { +
+ +
+} diff --git a/src/app/shared/components/contributors-list/contributors-list.component.scss b/src/app/shared/components/contributors-list/contributors-list.component.scss index e69de29bb..b9bc65ea4 100644 --- a/src/app/shared/components/contributors-list/contributors-list.component.scss +++ b/src/app/shared/components/contributors-list/contributors-list.component.scss @@ -0,0 +1,3 @@ +:host { + width: 100%; +} diff --git a/src/app/shared/components/contributors-list/contributors-list.component.ts b/src/app/shared/components/contributors-list/contributors-list.component.ts index 89cc6ab67..b84335abc 100644 --- a/src/app/shared/components/contributors-list/contributors-list.component.ts +++ b/src/app/shared/components/contributors-list/contributors-list.component.ts @@ -1,19 +1,26 @@ import { TranslatePipe } from '@ngx-translate/core'; -import { ChangeDetectionStrategy, Component, input } from '@angular/core'; +import { Button } from 'primeng/button'; +import { Skeleton } from 'primeng/skeleton'; + +import { ChangeDetectionStrategy, Component, input, output } from '@angular/core'; import { RouterLink } from '@angular/router'; import { ContributorModel } from '@shared/models'; @Component({ selector: 'osf-contributors-list', - imports: [RouterLink, TranslatePipe], + imports: [RouterLink, TranslatePipe, Skeleton, Button], templateUrl: './contributors-list.component.html', styleUrl: './contributors-list.component.scss', changeDetection: ChangeDetectionStrategy.OnPush, }) export class ContributorsListComponent { contributors = input.required[]>(); + isLoading = input(false); + hasLoadMore = input(false); readonly = input(false); anonymous = input(false); + + loadMoreContributors = output(); } diff --git a/src/app/shared/components/contributors/add-contributor-dialog/add-contributor-dialog.component.html b/src/app/shared/components/contributors/add-contributor-dialog/add-contributor-dialog.component.html index 933bf2479..b235be0d0 100644 --- a/src/app/shared/components/contributors/add-contributor-dialog/add-contributor-dialog.component.html +++ b/src/app/shared/components/contributors/add-contributor-dialog/add-contributor-dialog.component.html @@ -11,7 +11,12 @@ } @else { @for (item of users(); track $index) { } @@ -65,6 +70,22 @@
} + @if (allowAddingContributorsFromParentProject() && this.isSearchState()) { +
+ + +
+ } +
([]); readonly components = signal([]); readonly resourceName = signal(''); + readonly parentResourceName = signal(''); + readonly allowAddingContributorsFromParentProject = signal(false); readonly contributorNames = computed(() => this.selectedUsers() @@ -82,6 +85,10 @@ export class AddContributorDialogComponent implements OnInit, OnDestroy { readonly hasComponents = computed(() => this.components().length > 0); readonly buttonLabel = computed(() => (this.isComponentsState() ? 'common.buttons.done' : 'common.buttons.next')); + constructor() { + this.setupEffects(); + } + ngOnInit(): void { this.initializeDialogData(); this.setSearchSubscription(); @@ -113,6 +120,10 @@ export class AddContributorDialogComponent implements OnInit, OnDestroy { } } + addSourceProjectContributors(): void { + this.closeDialogWithData(AddContributorType.ParentProject); + } + addUnregistered(): void { this.dialogRef.close({ data: [], @@ -129,7 +140,8 @@ export class AddContributorDialogComponent implements OnInit, OnDestroy { private initializeDialogData(): void { this.selectedUsers.set([]); - const { components, resourceName } = this.config.data || {}; + const { components, resourceName, parentResourceName, allowAddingContributorsFromParentProject } = + this.config.data || {}; if (components) { this.components.set(components); @@ -138,16 +150,26 @@ export class AddContributorDialogComponent implements OnInit, OnDestroy { if (resourceName) { this.resourceName.set(resourceName); } + + if (allowAddingContributorsFromParentProject) { + this.allowAddingContributorsFromParentProject.set(allowAddingContributorsFromParentProject); + } + + if (parentResourceName) { + this.parentResourceName.set(parentResourceName); + } } - private closeDialogWithData(): void { + private closeDialogWithData(AddContributorTypeValue = AddContributorType.Registered): void { const childNodeIds = this.components() .filter((c) => c.checked && !c.isCurrent) .map((c) => c.id); + const filteredUsers = this.selectedUsers().filter((user) => !user.disabled); + this.dialogRef.close({ - data: this.selectedUsers(), - type: AddContributorType.Registered, + data: filteredUsers, + type: AddContributorTypeValue, childNodeIds: childNodeIds.length > 0 ? childNodeIds : undefined, } as ContributorDialogAddModel); } @@ -174,4 +196,18 @@ export class AddContributorDialogComponent implements OnInit, OnDestroy { this.currentPage.set(1); this.first.set(0); } + + private setupEffects(): void { + effect(() => { + const usersList = this.users(); + + if (usersList.length > 0) { + const checkedUsers = usersList.filter((user) => user.checked); + + if (checkedUsers.length > 0) { + this.selectedUsers.set(checkedUsers); + } + } + }); + } } diff --git a/src/app/shared/components/contributors/add-contributor-item/add-contributor-item.component.html b/src/app/shared/components/contributors/add-contributor-item/add-contributor-item.component.html index 239a1b4c2..76fd7d072 100644 --- a/src/app/shared/components/contributors/add-contributor-item/add-contributor-item.component.html +++ b/src/app/shared/components/contributors/add-contributor-item/add-contributor-item.component.html @@ -25,7 +25,7 @@
-
-

{{ 'project.overview.metadata.license' | translate }}

+

{{ 'common.labels.license' | translate }}

{{ resource.license?.name ?? ('project.overview.metadata.noLicense' | translate) }}
diff --git a/src/app/shared/components/resource-metadata/resource-metadata.component.ts b/src/app/shared/components/resource-metadata/resource-metadata.component.ts index f1ef25b3a..b83423090 100644 --- a/src/app/shared/components/resource-metadata/resource-metadata.component.ts +++ b/src/app/shared/components/resource-metadata/resource-metadata.component.ts @@ -10,7 +10,7 @@ import { Router, RouterLink } from '@angular/router'; import { ENVIRONMENT } from '@core/provider/environment.provider'; import { OverviewCollectionsComponent } from '@osf/features/project/overview/components/overview-collections/overview-collections.component'; import { CurrentResourceType } from '@osf/shared/enums'; -import { ResourceOverview } from '@shared/models'; +import { ContributorModel, ResourceOverview } from '@shared/models'; import { AffiliatedInstitutionsViewComponent } from '../affiliated-institutions-view/affiliated-institutions-view.component'; import { ContributorsListComponent } from '../contributors-list/contributors-list.component'; @@ -44,6 +44,10 @@ export class ResourceMetadataComponent { isCollectionsRoute = input(false); canEdit = input.required(); showEditButton = input(); + bibliographicContributors = input([]); + isBibliographicContributorsLoading = input(false); + hasMoreBibliographicContributors = input(false); + loadMoreContributors = output(); readonly resourceTypes = CurrentResourceType; readonly dateFormat = 'MMM d, y, h:mm a'; diff --git a/src/app/shared/components/subjects/subjects.component.spec.ts b/src/app/shared/components/subjects/subjects.component.spec.ts index 257ca3d16..54b62ea68 100644 --- a/src/app/shared/components/subjects/subjects.component.spec.ts +++ b/src/app/shared/components/subjects/subjects.component.spec.ts @@ -1,6 +1,6 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; -import { SubjectsSelectors } from '@osf/shared/stores'; +import { SubjectsSelectors } from '@osf/shared/stores/subjects'; import { SubjectsComponent } from './subjects.component'; diff --git a/src/app/shared/components/subjects/subjects.component.ts b/src/app/shared/components/subjects/subjects.component.ts index 2b986b7dc..63f86297b 100644 --- a/src/app/shared/components/subjects/subjects.component.ts +++ b/src/app/shared/components/subjects/subjects.component.ts @@ -15,7 +15,7 @@ import { ChangeDetectionStrategy, Component, computed, input, output } from '@an import { FormControl, FormsModule } from '@angular/forms'; import { SubjectModel } from '@osf/shared/models'; -import { SubjectsSelectors } from '@osf/shared/stores'; +import { SubjectsSelectors } from '@osf/shared/stores/subjects'; import { SearchInputComponent } from '../search-input/search-input.component'; diff --git a/src/app/shared/components/truncated-text/truncated-text.component.scss b/src/app/shared/components/truncated-text/truncated-text.component.scss index bb473e7e9..8c983db9c 100644 --- a/src/app/shared/components/truncated-text/truncated-text.component.scss +++ b/src/app/shared/components/truncated-text/truncated-text.component.scss @@ -4,7 +4,6 @@ text-overflow: ellipsis; display: -webkit-box; line-clamp: var(--line-clamp); - line-height: 1.7; -webkit-line-clamp: var(--line-clamp); -webkit-box-orient: vertical; white-space: pre-line; diff --git a/src/app/shared/components/wiki/add-wiki-dialog/add-wiki-dialog.component.spec.ts b/src/app/shared/components/wiki/add-wiki-dialog/add-wiki-dialog.component.spec.ts index c1b8b9399..06ea0bf4d 100644 --- a/src/app/shared/components/wiki/add-wiki-dialog/add-wiki-dialog.component.spec.ts +++ b/src/app/shared/components/wiki/add-wiki-dialog/add-wiki-dialog.component.spec.ts @@ -7,7 +7,7 @@ import { DynamicDialogConfig, DynamicDialogRef } from 'primeng/dynamicdialog'; import { ComponentFixture, TestBed } from '@angular/core/testing'; import { ReactiveFormsModule } from '@angular/forms'; -import { WikiSelectors } from '@osf/shared/stores'; +import { WikiSelectors } from '@osf/shared/stores/wiki'; import { ToastService } from '@shared/services'; import { AddWikiDialogComponent } from './add-wiki-dialog.component'; diff --git a/src/app/shared/components/wiki/add-wiki-dialog/add-wiki-dialog.component.ts b/src/app/shared/components/wiki/add-wiki-dialog/add-wiki-dialog.component.ts index 40e062cbe..4e80244de 100644 --- a/src/app/shared/components/wiki/add-wiki-dialog/add-wiki-dialog.component.ts +++ b/src/app/shared/components/wiki/add-wiki-dialog/add-wiki-dialog.component.ts @@ -12,7 +12,7 @@ import { InputLimits } from '@osf/shared/constants'; import { ResourceType } from '@osf/shared/enums'; import { CustomValidators } from '@osf/shared/helpers'; import { ToastService } from '@osf/shared/services'; -import { CreateWiki, WikiSelectors } from '@osf/shared/stores'; +import { CreateWiki, WikiSelectors } from '@osf/shared/stores/wiki'; import { TextInputComponent } from '../../text-input/text-input.component'; diff --git a/src/app/shared/constants/social-links.const.ts b/src/app/shared/constants/social-links.const.ts index a64cec645..56e3352f6 100644 --- a/src/app/shared/constants/social-links.const.ts +++ b/src/app/shared/constants/social-links.const.ts @@ -79,7 +79,7 @@ export const SOCIAL_LINKS: SocialLinksModel[] = [ address: '', placeholder: 'https://yourwebsite.com', key: 'profileWebsites', - icon: '', + icon: 'globe.svg', }, { id: 11, diff --git a/src/app/shared/enums/contributors/add-contributor-type.enum.ts b/src/app/shared/enums/contributors/add-contributor-type.enum.ts index 17ce058fc..cd0e360e4 100644 --- a/src/app/shared/enums/contributors/add-contributor-type.enum.ts +++ b/src/app/shared/enums/contributors/add-contributor-type.enum.ts @@ -1,4 +1,5 @@ export enum AddContributorType { Registered = 1, Unregistered, + ParentProject, } diff --git a/src/app/shared/mappers/collections/collections.mapper.ts b/src/app/shared/mappers/collections/collections.mapper.ts index 7a741e2d0..22969da39 100644 --- a/src/app/shared/mappers/collections/collections.mapper.ts +++ b/src/app/shared/mappers/collections/collections.mapper.ts @@ -37,12 +37,14 @@ export class CollectionsMapper { facebookAppId: response.attributes.facebook_app_id, allowSubmissions: response.attributes.allow_submissions, allowCommenting: response.attributes.allow_commenting, - assets: { - style: response.attributes.assets.style, - squareColorTransparent: response.attributes.assets.square_color_transparent, - squareColorNoTransparent: response.attributes.assets.square_color_no_transparent, - favicon: response.attributes.assets.favicon, - }, + assets: response.attributes.assets + ? { + style: response.attributes.assets.style, + squareColorTransparent: response.attributes.assets.square_color_transparent, + squareColorNoTransparent: response.attributes.assets.square_color_no_transparent, + favicon: response.attributes.assets.favicon, + } + : {}, shareSource: response.attributes.share_source, sharePublishType: response.attributes.share_publish_type, permissions: response.attributes.permissions, diff --git a/src/app/shared/mappers/identifiers.mapper.ts b/src/app/shared/mappers/identifiers.mapper.ts index 9db03b1dc..04d5cdaca 100644 --- a/src/app/shared/mappers/identifiers.mapper.ts +++ b/src/app/shared/mappers/identifiers.mapper.ts @@ -1,14 +1,16 @@ import { Identifier, IdentifiersResponseJsonApi } from '@shared/models'; export class IdentifiersMapper { - static fromJsonApi(response: IdentifiersResponseJsonApi): Identifier[] { - return response?.data.map((rawIdentifier) => { - return { - category: rawIdentifier.attributes.category, - value: rawIdentifier.attributes.value, - id: rawIdentifier.id, - type: rawIdentifier.type, - }; - }); + static fromJsonApi(response: IdentifiersResponseJsonApi | undefined): Identifier[] { + if (!response || !response.data) { + return []; + } + + return response?.data.map((rawIdentifier) => ({ + id: rawIdentifier.id, + type: rawIdentifier.type, + category: rawIdentifier.attributes.category, + value: rawIdentifier.attributes.value, + })); } } diff --git a/src/app/shared/mappers/licenses.mapper.ts b/src/app/shared/mappers/licenses.mapper.ts index aafaf8d67..f4726cfb3 100644 --- a/src/app/shared/mappers/licenses.mapper.ts +++ b/src/app/shared/mappers/licenses.mapper.ts @@ -2,10 +2,18 @@ import { LicenseDataJsonApi, LicenseModel, LicensesResponseJsonApi } from '../mo export class LicensesMapper { static fromLicensesResponse(response: LicensesResponseJsonApi): LicenseModel[] { - return response.data.map((item) => LicensesMapper.fromLicenseDataJsonApi(item)); + if (!response.data) { + return []; + } + + return response.data.map((item) => LicensesMapper.fromLicenseDataJsonApi(item)).filter((item) => !!item); } - static fromLicenseDataJsonApi(data: LicenseDataJsonApi): LicenseModel { + static fromLicenseDataJsonApi(data: LicenseDataJsonApi): LicenseModel | null { + if (!data) { + return null; + } + return { id: data?.id, name: data?.attributes?.name, diff --git a/src/app/shared/mappers/nodes/base-node.mapper.ts b/src/app/shared/mappers/nodes/base-node.mapper.ts index 75f0a79b7..771005e2b 100644 --- a/src/app/shared/mappers/nodes/base-node.mapper.ts +++ b/src/app/shared/mappers/nodes/base-node.mapper.ts @@ -60,6 +60,7 @@ export class BaseNodeMapper { wikiEnabled: data.attributes.wiki_enabled, customCitation: data.attributes.custom_citation || undefined, rootParentId: data.relationships.root?.data?.id, + parent: data.embeds?.parent?.data ? this.getNodeData(data.embeds?.parent.data) : undefined, }; } diff --git a/src/app/shared/mappers/registration/page-schema.mapper.ts b/src/app/shared/mappers/registration/page-schema.mapper.ts index 01fc419f3..e5207c49a 100644 --- a/src/app/shared/mappers/registration/page-schema.mapper.ts +++ b/src/app/shared/mappers/registration/page-schema.mapper.ts @@ -41,12 +41,18 @@ export class PageSchemaMapper { case BlockType.Paragraph: if (currentQuestion) { - currentQuestion.paragraphText = item.attributes.display_text; + currentQuestion.paragraphText = currentQuestion.paragraphText + ? currentQuestion.paragraphText + '\n \n' + item.attributes.display_text + : item.attributes.display_text; currentQuestion.fieldType = FieldType.Paragraph; } else if (currentSection) { - currentSection.description = item.attributes.display_text; + currentSection.description = currentSection.description + ? currentSection.description + '\n \n' + item.attributes.display_text + : item.attributes.display_text; } else { - currentPage.description = item.attributes.display_text; + currentPage.description = currentPage.description + ? currentPage.description + '\n \n' + item.attributes.display_text + : item.attributes.display_text; } break; diff --git a/src/app/shared/mappers/resource-overview.mappers.ts b/src/app/shared/mappers/resource-overview.mappers.ts index 419e515fa..cb4a69622 100644 --- a/src/app/shared/mappers/resource-overview.mappers.ts +++ b/src/app/shared/mappers/resource-overview.mappers.ts @@ -1,12 +1,13 @@ import { ProjectOverview } from '@osf/features/project/overview/models'; import { RegistryOverview } from '@osf/features/registry/models'; -import { Institution, ResourceOverview, SubjectModel } from '../models'; +import { ContributorModel, Institution, ResourceOverview, SubjectModel } from '../models'; export function MapProjectOverview( project: ProjectOverview, subjects: SubjectModel[], - isAnonymous = false + isAnonymous = false, + bibliographicContributors: ContributorModel[] = [] ): ResourceOverview { return { id: project.id, @@ -35,7 +36,7 @@ export function MapProjectOverview( currentUserIsContributorOrGroupMember: project.currentUserIsContributorOrGroupMember, wikiEnabled: project.wikiEnabled, subjects: subjects, - contributors: project.contributors?.filter(Boolean) || [], + contributors: bibliographicContributors?.filter(Boolean) || [], customCitation: project.customCitation || null, region: project.region || undefined, affiliatedInstitutions: project.affiliatedInstitutions?.filter(Boolean) || undefined, diff --git a/src/app/shared/mappers/view-only-links.mapper.ts b/src/app/shared/mappers/view-only-links.mapper.ts index a168611b2..3d56a1c4d 100644 --- a/src/app/shared/mappers/view-only-links.mapper.ts +++ b/src/app/shared/mappers/view-only-links.mapper.ts @@ -24,7 +24,7 @@ export class ViewOnlyLinksMapper { id: creator?.id || '', fullName: creator?.fullName || '', }, - nodes: item.embeds.nodes.data.map( + nodes: item.embeds?.nodes?.data?.map( (node) => ({ id: node.id, @@ -59,7 +59,7 @@ export class ViewOnlyLinksMapper { id: creator?.id || '', fullName: creator?.fullName || '', }, - nodes: item.embeds.nodes.data.map( + nodes: item.embeds?.nodes?.data?.map( (node) => ({ id: node.id, diff --git a/src/app/shared/models/contributors/contributor-add.model.ts b/src/app/shared/models/contributors/contributor-add.model.ts index 31bc3bf35..9e6a6f6c6 100644 --- a/src/app/shared/models/contributors/contributor-add.model.ts +++ b/src/app/shared/models/contributors/contributor-add.model.ts @@ -5,4 +5,6 @@ export interface ContributorAddModel { fullName?: string; email?: string; index?: number; + checked?: boolean; + disabled?: boolean; } diff --git a/src/app/shared/models/environment.model.ts b/src/app/shared/models/environment.model.ts index 03ee09262..9b399bbed 100644 --- a/src/app/shared/models/environment.model.ts +++ b/src/app/shared/models/environment.model.ts @@ -25,7 +25,7 @@ export interface EnvironmentModel { newRelicInfoSa: number; newRelicLoaderConfigAccountID: string; newRelicLoaderConfigTrustKey: string; - newRelicLoaderConfigAgengID: string; + newRelicLoaderConfigAgentID: string; newRelicLoaderConfigLicenseKey: string; newRelicLoaderConfigApplicationID: string; activityLogs?: { diff --git a/src/app/shared/models/nodes/base-node-embeds-json-api.model.ts b/src/app/shared/models/nodes/base-node-embeds-json-api.model.ts index 5f7ace796..ae0c580be 100644 --- a/src/app/shared/models/nodes/base-node-embeds-json-api.model.ts +++ b/src/app/shared/models/nodes/base-node-embeds-json-api.model.ts @@ -1,4 +1,5 @@ import { + BaseNodeDataJsonApi, ContributorDataJsonApi, IdentifierAttributes, IdentifiersJsonApiData, @@ -23,6 +24,9 @@ export interface BaseNodeEmbedsJsonApi { region?: { data: RegionDataJsonApi; }; + parent?: { + data: BaseNodeDataJsonApi; + }; } export interface JsonApiResource { diff --git a/src/app/shared/models/nodes/base-node.model.ts b/src/app/shared/models/nodes/base-node.model.ts index 4187db43e..0ef9ccb0d 100644 --- a/src/app/shared/models/nodes/base-node.model.ts +++ b/src/app/shared/models/nodes/base-node.model.ts @@ -23,6 +23,7 @@ export interface BaseNodeModel { wikiEnabled: boolean; rootParentId?: string; type: string; + parent?: BaseNodeModel; } export interface NodeModel extends BaseNodeModel { diff --git a/src/app/shared/services/contributors.service.ts b/src/app/shared/services/contributors.service.ts index 16d525413..7735c7f85 100644 --- a/src/app/shared/services/contributors.service.ts +++ b/src/app/shared/services/contributors.service.ts @@ -164,6 +164,12 @@ export class ContributorsService { .pipe(map((contributor) => ContributorsMapper.getContributor(contributor.data))); } + addContributorsFromProject(resourceType: ResourceType, resourceId: string): Observable { + const baseUrl = `${this.getBaseUrl(resourceType, resourceId)}/?copy_contributors_from_parent_project=true`; + const contributorData = { data: { type: AddContributorType.ParentProject } }; + return this.jsonApiService.patch(baseUrl, contributorData); + } + deleteContributor(resourceType: ResourceType, resourceId: string, userId: string): Observable { const baseUrl = `${this.getBaseUrl(resourceType, resourceId)}/${userId}/`; diff --git a/src/app/shared/services/resource.service.ts b/src/app/shared/services/resource.service.ts index fd227e040..839f2194f 100644 --- a/src/app/shared/services/resource.service.ts +++ b/src/app/shared/services/resource.service.ts @@ -66,9 +66,11 @@ export class ResourceGuidService { getResourceDetails(resourceId: string, resourceType: ResourceType): Observable { const resourcePath = this.urlMap.get(resourceType); - + const params: Record = { + embed: 'parent', + }; return this.jsonApiService - .get>(`${this.apiUrl}/${resourcePath}/${resourceId}/`) + .get>(`${this.apiUrl}/${resourcePath}/${resourceId}/`, params) .pipe(map((response) => BaseNodeMapper.getNodeData(response.data))); } diff --git a/src/app/shared/stores/contributors/contributors.actions.ts b/src/app/shared/stores/contributors/contributors.actions.ts index 4eaaead98..e2654c6c9 100644 --- a/src/app/shared/stores/contributors/contributors.actions.ts +++ b/src/app/shared/stores/contributors/contributors.actions.ts @@ -62,6 +62,15 @@ export class BulkAddContributors { ) {} } +export class BulkAddContributorsFromParentProject { + static readonly type = '[Contributors] Bulk Add Contributors From Parent Project'; + + constructor( + public resourceId: string | undefined | null, + public resourceType: ResourceType | undefined + ) {} +} + export class DeleteContributor { static readonly type = '[Contributors] Delete Contributor'; @@ -119,3 +128,32 @@ export class RejectRequestAccess { public resourceType: ResourceType | undefined ) {} } + +export class GetBibliographicContributors { + static readonly type = '[Contributors] Get Bibliographic Contributors'; + + constructor( + public resourceId: string | undefined | null, + public resourceType: ResourceType | undefined, + public page = 1, + public pageSize = DEFAULT_TABLE_PARAMS.rows + ) {} +} + +export class LoadMoreBibliographicContributors { + static readonly type = '[Contributors] Load More Bibliographic Contributors'; + + constructor( + public resourceId: string | undefined | null, + public resourceType: ResourceType | undefined + ) {} +} + +export class LoadMoreContributors { + static readonly type = '[Contributors] Load More Contributors'; + + constructor( + public resourceId: string | undefined | null, + public resourceType: ResourceType | undefined + ) {} +} diff --git a/src/app/shared/stores/contributors/contributors.model.ts b/src/app/shared/stores/contributors/contributors.model.ts index 5f7c92b61..89d34a7fd 100644 --- a/src/app/shared/stores/contributors/contributors.model.ts +++ b/src/app/shared/stores/contributors/contributors.model.ts @@ -2,16 +2,21 @@ import { DEFAULT_TABLE_PARAMS } from '@osf/shared/constants'; import { ContributorAddModel, ContributorModel, RequestAccessModel } from '@osf/shared/models'; import { AsyncStateModel, AsyncStateWithTotalCount } from '@osf/shared/models/store'; -export interface ContributorsListModel extends AsyncStateWithTotalCount { +export interface ContributorsList extends AsyncStateWithTotalCount { + page: number; + pageSize: number; +} + +export interface ContributorsListWithFiltersModel extends ContributorsList { searchValue: string | null; permissionFilter: string | null; bibliographyFilter: boolean | null; - page: number; - pageSize: number; + isLoadingMore: boolean; } export interface ContributorsStateModel { - contributorsList: ContributorsListModel; + contributorsList: ContributorsListWithFiltersModel; + bibliographicContributorsList: ContributorsList; requestAccessList: AsyncStateModel; users: AsyncStateWithTotalCount; } @@ -27,6 +32,16 @@ export const CONTRIBUTORS_STATE_DEFAULTS: ContributorsStateModel = { totalCount: 0, page: 1, pageSize: DEFAULT_TABLE_PARAMS.rows, + isLoadingMore: false, + }, + bibliographicContributorsList: { + data: [], + isLoading: false, + isSubmitting: false, + error: null, + totalCount: 0, + page: 0, + pageSize: DEFAULT_TABLE_PARAMS.rows, }, requestAccessList: { data: [], diff --git a/src/app/shared/stores/contributors/contributors.selectors.ts b/src/app/shared/stores/contributors/contributors.selectors.ts index f3816a2de..57f026482 100644 --- a/src/app/shared/stores/contributors/contributors.selectors.ts +++ b/src/app/shared/stores/contributors/contributors.selectors.ts @@ -30,11 +30,29 @@ export class ContributorsSelectors { @Selector([ContributorsState]) static getBibliographicContributors(state: ContributorsStateModel) { - if (!state?.contributorsList?.data) { + if (!state?.bibliographicContributorsList?.data) { return []; } - return state.contributorsList.data.filter((contributor) => contributor.isBibliographic); + return state.bibliographicContributorsList.data; + } + + @Selector([ContributorsState]) + static isBibliographicContributorsLoading(state: ContributorsStateModel) { + return state?.bibliographicContributorsList?.isLoading || false; + } + + @Selector([ContributorsState]) + static getBibliographicContributorsTotalCount(state: ContributorsStateModel) { + return state?.bibliographicContributorsList?.totalCount || 0; + } + + @Selector([ContributorsState]) + static hasMoreBibliographicContributors(state: ContributorsStateModel) { + return ( + state?.bibliographicContributorsList?.data?.length < state?.bibliographicContributorsList?.totalCount && + !state?.bibliographicContributorsList?.isLoading + ); } @Selector([ContributorsState]) @@ -43,8 +61,8 @@ export class ContributorsSelectors { } @Selector([ContributorsState]) - static getContributorsPageNumber(state: ContributorsStateModel) { - return state.contributorsList.page; + static isContributorsLoadingMore(state: ContributorsStateModel) { + return state?.contributorsList?.isLoadingMore || false; } @Selector([ContributorsState]) @@ -57,6 +75,13 @@ export class ContributorsSelectors { return state.contributorsList.totalCount; } + @Selector([ContributorsState]) + static hasMoreContributors(state: ContributorsStateModel) { + return ( + state?.contributorsList?.data?.length < state?.contributorsList?.totalCount && !state?.contributorsList?.isLoading + ); + } + @Selector([ContributorsState]) static getUsers(state: ContributorsStateModel) { return state?.users?.data || []; diff --git a/src/app/shared/stores/contributors/contributors.state.ts b/src/app/shared/stores/contributors/contributors.state.ts index be0259ff5..cd894866a 100644 --- a/src/app/shared/stores/contributors/contributors.state.ts +++ b/src/app/shared/stores/contributors/contributors.state.ts @@ -11,11 +11,15 @@ import { AcceptRequestAccess, AddContributor, BulkAddContributors, + BulkAddContributorsFromParentProject, BulkUpdateContributors, ClearUsers, DeleteContributor, GetAllContributors, + GetBibliographicContributors, GetRequestAccessContributors, + LoadMoreBibliographicContributors, + LoadMoreContributors, RejectRequestAccess, ResetContributorsState, SearchUsers, @@ -48,19 +52,23 @@ export class ContributorsState { ctx.patchState({ contributorsList: { ...state.contributorsList, - data: [], - isLoading: true, + data: page === 1 ? [] : state.contributorsList.data, + isLoading: page === 1, + isLoadingMore: page > 1, error: null, }, }); return this.contributorsService.getAllContributors(action.resourceType, action.resourceId, page, pageSize).pipe( tap((res) => { + const data = page === 1 ? res.data : [...state.contributorsList.data, ...res.data]; + ctx.patchState({ contributorsList: { ...state.contributorsList, - data: res.data, + data, isLoading: false, + isLoadingMore: false, totalCount: res.totalCount, page, pageSize, @@ -206,6 +214,29 @@ export class ContributorsState { ); } + @Action(BulkAddContributorsFromParentProject) + bulkAddContributorsFromParentProject( + ctx: StateContext, + action: BulkAddContributorsFromParentProject + ) { + const state = ctx.getState(); + + if (!action.resourceId || !action.resourceType) { + return; + } + + ctx.patchState({ + contributorsList: { ...state.contributorsList, isLoading: true, error: null }, + }); + + return this.contributorsService.addContributorsFromProject(action.resourceType, action.resourceId).pipe( + tap(() => { + ctx.dispatch(new GetAllContributors(action.resourceId, action.resourceType)); + }), + catchError((error) => handleSectionError(ctx, 'contributorsList', error)) + ); + } + @Action(DeleteContributor) deleteContributor(ctx: StateContext, action: DeleteContributor) { const state = ctx.getState(); @@ -259,17 +290,21 @@ export class ContributorsState { users: { ...state.users, isLoading: true, error: null }, }); - const addedContributorsIds = state.contributorsList.data.map((contributor) => contributor.userId); - if (!action.searchValue) { return of([]); } return this.contributorsService.searchUsers(action.searchValue, action.page).pipe( tap((users) => { + const addedContributorsIds = state.contributorsList.data.map((contributor) => contributor.userId); + ctx.patchState({ users: { - data: users.data.filter((user) => !addedContributorsIds.includes(user.id!)), + data: users.data.map((user) => ({ + ...user, + checked: addedContributorsIds.includes(user.id!), + disabled: addedContributorsIds.includes(user.id!), + })), isLoading: false, error: '', totalCount: users.totalCount, @@ -285,6 +320,67 @@ export class ContributorsState { ctx.patchState({ users: { data: [], isLoading: false, error: null, totalCount: 0 } }); } + @Action(GetBibliographicContributors) + getBibliographicContributors(ctx: StateContext, action: GetBibliographicContributors) { + const state = ctx.getState(); + + if (!action.resourceId || !action.resourceType) { + return; + } + + ctx.patchState({ + bibliographicContributorsList: { + ...state.bibliographicContributorsList, + data: action.page === 1 ? [] : state.bibliographicContributorsList.data, + isLoading: true, + error: null, + }, + }); + + return this.contributorsService + .getBibliographicContributors(action.resourceType, action.resourceId, action.page, action.pageSize) + .pipe( + tap((res) => { + const data = action.page === 1 ? res.data : [...state.bibliographicContributorsList.data, ...res.data]; + + ctx.patchState({ + bibliographicContributorsList: { + data, + isLoading: false, + error: null, + page: action.page, + pageSize: res.pageSize, + totalCount: res.totalCount, + }, + }); + }), + catchError((error) => handleSectionError(ctx, 'bibliographicContributorsList', error)) + ); + } + + @Action(LoadMoreBibliographicContributors) + loadMoreBibliographicContributors( + ctx: StateContext, + action: LoadMoreBibliographicContributors + ) { + const state = ctx.getState(); + const nextPage = state.bibliographicContributorsList.page + 1; + const nextPageSize = state.bibliographicContributorsList.pageSize; + + return ctx.dispatch( + new GetBibliographicContributors(action.resourceId, action.resourceType, nextPage, nextPageSize) + ); + } + + @Action(LoadMoreContributors) + loadMoreContributors(ctx: StateContext, action: LoadMoreContributors) { + const state = ctx.getState(); + const nextPage = state.contributorsList.page + 1; + const nextPageSize = state.contributorsList.pageSize; + + return ctx.dispatch(new GetAllContributors(action.resourceId, action.resourceType, nextPage, nextPageSize)); + } + @Action(ResetContributorsState) resetState(ctx: StateContext) { ctx.setState({ ...CONTRIBUTORS_STATE_DEFAULTS }); diff --git a/src/app/shared/stores/index.ts b/src/app/shared/stores/index.ts deleted file mode 100644 index bc94b95fa..000000000 --- a/src/app/shared/stores/index.ts +++ /dev/null @@ -1,17 +0,0 @@ -export * from './addons'; -export * from './banners'; -export * from './bookmarks'; -export * from './citations'; -export * from './collections'; -export * from './contributors'; -export * from './current-resource'; -export * from './duplicates'; -export * from './institutions'; -export * from './licenses'; -export * from './my-resources'; -export * from './node-links'; -export * from './projects'; -export * from './regions'; -export * from './subjects'; -export * from './view-only-links'; -export * from './wiki'; diff --git a/src/assets/config/template.json b/src/assets/config/template.json index 9849e4300..55891f4a1 100644 --- a/src/assets/config/template.json +++ b/src/assets/config/template.json @@ -12,29 +12,6 @@ "googleTagManagerId": "", "googleFilePickerApiKey": "", "googleFilePickerAppId": 0, - "newRelic": { - "enabled": false, - "init": { - "distributed_tracing": { "enabled": false }, - "performance": { "capture_measures": false }, - "privacy": { "cookies_enabled": true }, - "ajax": { "deny_list": [""] } - }, - "info": { - "beacon": "", - "errorBeacon": "", - "licenseKey": "", - "applicationID": "", - "sa": 1 - }, - "loader_config": { - "accountID": "", - "trustKey": "", - "agentID": "", - "licenseKey": "", - "applicationID": "" - } - }, "newRelicEnabled": true, "newRelicInitDistributedTracingEnabled": false, "newRelicInitPerformanceCaptureMeasures": false, @@ -47,7 +24,7 @@ "newRelicInfoSa": 1, "newRelicLoaderConfigAccountID": "", "newRelicLoaderConfigTrustKey": "", - "newRelicLoaderConfigAgengID": "", + "newRelicLoaderConfigAgentID": "", "newRelicLoaderConfigLicenseKey": "", "newRelicLoaderConfigApplicationID": "" } diff --git a/src/assets/i18n/en.json b/src/assets/i18n/en.json index 58c0b767d..11e832513 100644 --- a/src/assets/i18n/en.json +++ b/src/assets/i18n/en.json @@ -57,7 +57,8 @@ "removeAll": "Remove All", "accept": "Accept", "reject": "Reject", - "loadMore": "Load more" + "loadMore": "Load more", + "seeMore": "See more" }, "accessibility": { "help": "Help", @@ -135,7 +136,8 @@ "contributors": "Contributors", "updated": "Updated", "dateUpdated": "Date Updated", - "dateCreated": "Date Created" + "dateCreated": "Date Created", + "license": "License" }, "deleteConfirmation": { "header": "Delete", @@ -606,7 +608,8 @@ "bibliographicContributor": "Bibliographic Contributor", "addUnregisteredContributor": "Add Unregistered Contributor", "addRegisteredContributor": "Add Registered Contributor", - "unregisteredContributorNotification": "We will notify the user that they have been added to your project" + "unregisteredContributorNotification": "We will notify the user that they have been added to your project", + "addingContributorsFromParentProject": "Import contributors from {{projectName}}" }, "removeDialog": { "title": "Remove contributor", @@ -718,7 +721,6 @@ "supplementsText2": "on OSF Preprints", "dateCreated": "Date Created", "dateUpdated": "Date Updated", - "license": "License", "projectDOI": "Project DOI", "publicationDOI": "Publication DOI", "registrationDOI": "Registration DOI", @@ -1157,7 +1159,9 @@ "storage": "OSF Storage", "pathError": "Path is not specified!", "success": "Successfully moved.", - "noMovePermission": "Cannot move or copy to this file provider" + "noMovePermission": "Cannot move or copy to this file provider", + "movingHeader": "Moving...", + "copingHeader": "Coping..." }, "copyFile": { "success": "File successfully copied." @@ -1382,7 +1386,6 @@ "title": "Title", "description": "Description", "chooseLicense": "Choose License", - "license": "License", "licensePlaceholder": "Select license", "tags": "Tags", "tagsPlaceholder": "Add tags here", @@ -2216,7 +2219,6 @@ "contributors": "Contributors", "authors": "Authors", "affiliatedInstitutions": "Affiliated Institutions", - "license": "License", "publicationDoi": "Publication DOI", "subjects": "Subjects", "tags": "Tags", @@ -2737,7 +2739,6 @@ "description": "Description:", "descriptionBold": "Description: ", "provider": "Provider:", - "license": "License:", "context": "Context", "registrationTemplate": "Registration Template:", "conflictOfInterestResponse": "Conflict of Interest response:", diff --git a/src/assets/icons/socials/globe.svg b/src/assets/icons/socials/globe.svg new file mode 100644 index 000000000..938451a18 --- /dev/null +++ b/src/assets/icons/socials/globe.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/js/newrelic/newrelic.snippet.js b/src/assets/js/newrelic/newrelic.snippet.js new file mode 100644 index 000000000..e63e4fc1d --- /dev/null +++ b/src/assets/js/newrelic/newrelic.snippet.js @@ -0,0 +1,3319 @@ +window.NREUM || (NREUM = {}); +NREUM.init = { + distributed_tracing: { enabled: true }, + performance: { capture_measures: true }, + privacy: { cookies_enabled: true }, + ajax: { deny_list: ['bam.nr-data.net'] }, +}; + +NREUM.loader_config = { + accountID: '772413', + trustKey: '772413', + agentID: '1835137194', + licenseKey: '704513e63b', + applicationID: '1835137194', +}; +NREUM.info = { + beacon: 'bam.nr-data.net', + errorBeacon: 'bam.nr-data.net', + licenseKey: '704513e63b', + applicationID: '1835137194', + sa: 1, +}; /*! For license information please see nr-loader-spa-1.301.0.min.js.LICENSE.txt */ +(() => { + var e, + t, + r = { + 384: (e, t, r) => { + 'use strict'; + r.d(t, { NT: () => a, US: () => d, Zm: () => s, bQ: () => u, dV: () => c, pV: () => l }); + var n = r(6154), + i = r(1863), + o = r(1910); + const a = { beacon: 'bam.nr-data.net', errorBeacon: 'bam.nr-data.net' }; + function s() { + return n.gm.NREUM || (n.gm.NREUM = {}), void 0 === n.gm.newrelic && (n.gm.newrelic = n.gm.NREUM), n.gm.NREUM; + } + function c() { + let e = s(); + return ( + e.o || + ((e.o = { + ST: n.gm.setTimeout, + SI: n.gm.setImmediate || n.gm.setInterval, + CT: n.gm.clearTimeout, + XHR: n.gm.XMLHttpRequest, + REQ: n.gm.Request, + EV: n.gm.Event, + PR: n.gm.Promise, + MO: n.gm.MutationObserver, + FETCH: n.gm.fetch, + WS: n.gm.WebSocket, + }), + (0, o.i)(...Object.values(e.o))), + e + ); + } + function u(e, t) { + let r = s(); + (r.initializedAgents ??= {}), + (t.initializedAt = { ms: (0, i.t)(), date: new Date() }), + (r.initializedAgents[e] = t); + } + function d(e, t) { + s()[e] = t; + } + function l() { + return ( + (function () { + let e = s(); + const t = e.info || {}; + e.info = { beacon: a.beacon, errorBeacon: a.errorBeacon, ...t }; + })(), + (function () { + let e = s(); + const t = e.init || {}; + e.init = { ...t }; + })(), + c(), + (function () { + let e = s(); + const t = e.loader_config || {}; + e.loader_config = { ...t }; + })(), + s() + ); + } + }, + 782: (e, t, r) => { + 'use strict'; + r.d(t, { T: () => n }); + const n = r(860).K7.pageViewTiming; + }, + 860: (e, t, r) => { + 'use strict'; + r.d(t, { + $J: () => d, + K7: () => c, + P3: () => u, + XX: () => i, + Yy: () => s, + df: () => o, + qY: () => n, + v4: () => a, + }); + const n = 'events', + i = 'jserrors', + o = 'browser/blobs', + a = 'rum', + s = 'browser/logs', + c = { + ajax: 'ajax', + genericEvents: 'generic_events', + jserrors: i, + logging: 'logging', + metrics: 'metrics', + pageAction: 'page_action', + pageViewEvent: 'page_view_event', + pageViewTiming: 'page_view_timing', + sessionReplay: 'session_replay', + sessionTrace: 'session_trace', + softNav: 'soft_navigations', + spa: 'spa', + }, + u = { + [c.pageViewEvent]: 1, + [c.pageViewTiming]: 2, + [c.metrics]: 3, + [c.jserrors]: 4, + [c.spa]: 5, + [c.ajax]: 6, + [c.sessionTrace]: 7, + [c.softNav]: 8, + [c.sessionReplay]: 9, + [c.logging]: 10, + [c.genericEvents]: 11, + }, + d = { + [c.pageViewEvent]: a, + [c.pageViewTiming]: n, + [c.ajax]: n, + [c.spa]: n, + [c.softNav]: n, + [c.metrics]: i, + [c.jserrors]: i, + [c.sessionTrace]: o, + [c.sessionReplay]: o, + [c.logging]: s, + [c.genericEvents]: 'ins', + }; + }, + 944: (e, t, r) => { + 'use strict'; + r.d(t, { R: () => i }); + var n = r(3241); + function i(e, t) { + 'function' == typeof console.debug && + (console.debug( + 'New Relic Warning: https://github.com/newrelic/newrelic-browser-agent/blob/main/docs/warning-codes.md#'.concat( + e + ), + t + ), + (0, n.W)({ + agentIdentifier: null, + drained: null, + type: 'data', + name: 'warn', + feature: 'warn', + data: { code: e, secondary: t }, + })); + } + }, + 993: (e, t, r) => { + 'use strict'; + r.d(t, { A$: () => o, ET: () => a, TZ: () => s, p_: () => i }); + var n = r(860); + const i = { ERROR: 'ERROR', WARN: 'WARN', INFO: 'INFO', DEBUG: 'DEBUG', TRACE: 'TRACE' }, + o = { OFF: 0, ERROR: 1, WARN: 2, INFO: 3, DEBUG: 4, TRACE: 5 }, + a = 'log', + s = n.K7.logging; + }, + 1687: (e, t, r) => { + 'use strict'; + r.d(t, { Ak: () => u, Ze: () => f, x3: () => d }); + var n = r(3241), + i = r(7836), + o = r(3606), + a = r(860), + s = r(2646); + const c = {}; + function u(e, t) { + const r = { staged: !1, priority: a.P3[t] || 0 }; + l(e), c[e].get(t) || c[e].set(t, r); + } + function d(e, t) { + e && c[e] && (c[e].get(t) && c[e].delete(t), p(e, t, !1), c[e].size && h(e)); + } + function l(e) { + if (!e) throw new Error('agentIdentifier required'); + c[e] || (c[e] = new Map()); + } + function f(e = '', t = 'feature', r = !1) { + if ((l(e), !e || !c[e].get(t) || r)) return p(e, t); + (c[e].get(t).staged = !0), h(e); + } + function h(e) { + const t = Array.from(c[e]); + t.every(([e, t]) => t.staged) && + (t.sort((e, t) => e[1].priority - t[1].priority), + t.forEach(([t]) => { + c[e].delete(t), p(e, t); + })); + } + function p(e, t, r = !0) { + const a = e ? i.ee.get(e) : i.ee, + c = o.i.handlers; + if (!a.aborted && a.backlog && c) { + if (((0, n.W)({ agentIdentifier: e, type: 'lifecycle', name: 'drain', feature: t }), r)) { + const e = a.backlog[t], + r = c[t]; + if (r) { + for (let t = 0; e && t < e.length; ++t) g(e[t], r); + Object.entries(r).forEach(([e, t]) => { + Object.values(t || {}).forEach((t) => { + t[0]?.on && t[0]?.context() instanceof s.y && t[0].on(e, t[1]); + }); + }); + } + } + a.isolatedBacklog || delete c[t], (a.backlog[t] = null), a.emit('drain-' + t, []); + } + } + function g(e, t) { + var r = e[1]; + Object.values(t[r] || {}).forEach((t) => { + var r = e[0]; + if (t[0] === r) { + var n = t[1], + i = e[3], + o = e[2]; + n.apply(i, o); + } + }); + } + }, + 1741: (e, t, r) => { + 'use strict'; + r.d(t, { W: () => o }); + var n = r(944), + i = r(4261); + class o { + #e(e, ...t) { + if (this[e] !== o.prototype[e]) return this[e](...t); + (0, n.R)(35, e); + } + addPageAction(e, t) { + return this.#e(i.hG, e, t); + } + register(e) { + return this.#e(i.eY, e); + } + recordCustomEvent(e, t) { + return this.#e(i.fF, e, t); + } + setPageViewName(e, t) { + return this.#e(i.Fw, e, t); + } + setCustomAttribute(e, t, r) { + return this.#e(i.cD, e, t, r); + } + noticeError(e, t) { + return this.#e(i.o5, e, t); + } + setUserId(e) { + return this.#e(i.Dl, e); + } + setApplicationVersion(e) { + return this.#e(i.nb, e); + } + setErrorHandler(e) { + return this.#e(i.bt, e); + } + addRelease(e, t) { + return this.#e(i.k6, e, t); + } + log(e, t) { + return this.#e(i.$9, e, t); + } + start() { + return this.#e(i.d3); + } + finished(e) { + return this.#e(i.BL, e); + } + recordReplay() { + return this.#e(i.CH); + } + pauseReplay() { + return this.#e(i.Tb); + } + addToTrace(e) { + return this.#e(i.U2, e); + } + setCurrentRouteName(e) { + return this.#e(i.PA, e); + } + interaction(e) { + return this.#e(i.dT, e); + } + wrapLogger(e, t, r) { + return this.#e(i.Wb, e, t, r); + } + measure(e, t) { + return this.#e(i.V1, e, t); + } + } + }, + 1863: (e, t, r) => { + 'use strict'; + function n() { + return Math.floor(performance.now()); + } + r.d(t, { t: () => n }); + }, + 1910: (e, t, r) => { + 'use strict'; + r.d(t, { i: () => o }); + var n = r(944); + const i = new Map(); + function o(...e) { + return e.every((e) => { + if (i.has(e)) return i.get(e); + const t = 'function' == typeof e && e.toString().includes('[native code]'); + return t || (0, n.R)(64, e?.name || e?.toString()), i.set(e, t), t; + }); + } + }, + 2555: (e, t, r) => { + 'use strict'; + r.d(t, { D: () => s, f: () => a }); + var n = r(384), + i = r(8122); + const o = { + beacon: n.NT.beacon, + errorBeacon: n.NT.errorBeacon, + licenseKey: void 0, + applicationID: void 0, + sa: void 0, + queueTime: void 0, + applicationTime: void 0, + ttGuid: void 0, + user: void 0, + account: void 0, + product: void 0, + extra: void 0, + jsAttributes: {}, + userAttributes: void 0, + atts: void 0, + transactionName: void 0, + tNamePlain: void 0, + }; + function a(e) { + try { + return !!e.licenseKey && !!e.errorBeacon && !!e.applicationID; + } catch (e) { + return !1; + } + } + const s = (e) => (0, i.a)(e, o); + }, + 2614: (e, t, r) => { + 'use strict'; + r.d(t, { BB: () => a, H3: () => n, g: () => u, iL: () => c, tS: () => s, uh: () => i, wk: () => o }); + const n = 'NRBA', + i = 'SESSION', + o = 144e5, + a = 18e5, + s = { + STARTED: 'session-started', + PAUSE: 'session-pause', + RESET: 'session-reset', + RESUME: 'session-resume', + UPDATE: 'session-update', + }, + c = { SAME_TAB: 'same-tab', CROSS_TAB: 'cross-tab' }, + u = { OFF: 0, FULL: 1, ERROR: 2 }; + }, + 2646: (e, t, r) => { + 'use strict'; + r.d(t, { y: () => n }); + class n { + constructor(e) { + this.contextId = e; + } + } + }, + 2843: (e, t, r) => { + 'use strict'; + r.d(t, { u: () => i }); + var n = r(3878); + function i(e, t = !1, r, i) { + (0, n.DD)( + 'visibilitychange', + function () { + if (t) return void ('hidden' === document.visibilityState && e()); + e(document.visibilityState); + }, + r, + i + ); + } + }, + 3241: (e, t, r) => { + 'use strict'; + r.d(t, { W: () => o }); + var n = r(6154); + const i = 'newrelic'; + function o(e = {}) { + try { + n.gm.dispatchEvent(new CustomEvent(i, { detail: e })); + } catch (e) {} + } + }, + 3304: (e, t, r) => { + 'use strict'; + r.d(t, { A: () => o }); + var n = r(7836); + const i = () => { + const e = new WeakSet(); + return (t, r) => { + if ('object' == typeof r && null !== r) { + if (e.has(r)) return; + e.add(r); + } + return r; + }; + }; + function o(e) { + try { + return JSON.stringify(e, i()) ?? ''; + } catch (e) { + try { + n.ee.emit('internal-error', [e]); + } catch (e) {} + return ''; + } + } + }, + 3333: (e, t, r) => { + 'use strict'; + r.d(t, { + $v: () => d, + TZ: () => n, + Xh: () => c, + Zp: () => i, + kd: () => u, + mq: () => s, + nf: () => a, + qN: () => o, + }); + const n = r(860).K7.genericEvents, + i = ['auxclick', 'click', 'copy', 'keydown', 'paste', 'scrollend'], + o = ['focus', 'blur'], + a = 4, + s = 1e3, + c = 2e3, + u = ['PageAction', 'UserAction', 'BrowserPerformance'], + d = { RESOURCES: 'experimental.resources' }; + }, + 3434: (e, t, r) => { + 'use strict'; + r.d(t, { Jt: () => o, YM: () => u }); + var n = r(7836), + i = r(5607); + const o = 'nr@original:'.concat(i.W), + a = 50; + var s = Object.prototype.hasOwnProperty, + c = !1; + function u(e, t) { + return ( + e || (e = n.ee), + (r.inPlace = function (e, t, n, i, o) { + n || (n = ''); + const a = '-' === n.charAt(0); + for (let s = 0; s < t.length; s++) { + const c = t[s], + u = e[c]; + l(u) || (e[c] = r(u, a ? c + n : n, i, c, o)); + } + }), + (r.flag = o), + r + ); + function r(t, r, n, c, u) { + return l(t) + ? t + : (r || (r = ''), + (nrWrapper[o] = t), + (function (e, t, r) { + if (Object.defineProperty && Object.keys) + try { + return ( + Object.keys(e).forEach(function (r) { + Object.defineProperty(t, r, { + get: function () { + return e[r]; + }, + set: function (t) { + return (e[r] = t), t; + }, + }); + }), + t + ); + } catch (e) { + d([e], r); + } + for (var n in e) s.call(e, n) && (t[n] = e[n]); + })(t, nrWrapper, e), + nrWrapper); + function nrWrapper() { + var o, s, l, f; + let h; + try { + (s = this), (o = [...arguments]), (l = 'function' == typeof n ? n(o, s) : n || {}); + } catch (t) { + d([t, '', [o, s, c], l], e); + } + i(r + 'start', [o, s, c], l, u); + const p = performance.now(); + let g; + try { + return (f = t.apply(s, o)), (g = performance.now()), f; + } catch (e) { + throw ((g = performance.now()), i(r + 'err', [o, s, e], l, u), (h = e), h); + } finally { + const e = g - p, + t = { start: p, end: g, duration: e, isLongTask: e >= a, methodName: c, thrownError: h }; + t.isLongTask && i('long-task', [t, s], l, u), i(r + 'end', [o, s, f], l, u); + } + } + } + function i(r, n, i, o) { + if (!c || t) { + var a = c; + c = !0; + try { + e.emit(r, n, i, t, o); + } catch (t) { + d([t, r, n, i], e); + } + c = a; + } + } + } + function d(e, t) { + t || (t = n.ee); + try { + t.emit('internal-error', e); + } catch (e) {} + } + function l(e) { + return !(e && 'function' == typeof e && e.apply && !e[o]); + } + }, + 3496: (e, t, r) => { + 'use strict'; + function n(e) { + return !e || !(!e.licenseKey || !e.applicationID); + } + function i(e, t) { + return !e || (e.licenseKey === t.info.licenseKey && e.applicationID === t.info.applicationID); + } + r.d(t, { A: () => i, I: () => n }); + }, + 3606: (e, t, r) => { + 'use strict'; + r.d(t, { i: () => o }); + var n = r(9908); + o.on = a; + var i = (o.handlers = {}); + function o(e, t, r, o) { + a(o || n.d, i, e, t, r); + } + function a(e, t, r, i, o) { + o || (o = 'feature'), e || (e = n.d); + var a = (t[o] = t[o] || {}); + (a[r] = a[r] || []).push([e, i]); + } + }, + 3738: (e, t, r) => { + 'use strict'; + r.d(t, { + He: () => i, + Kp: () => s, + Lc: () => u, + Rz: () => d, + TZ: () => n, + bD: () => o, + d3: () => a, + jx: () => l, + sl: () => f, + uP: () => c, + }); + const n = r(860).K7.sessionTrace, + i = 'bstResource', + o = 'resource', + a = '-start', + s = '-end', + c = 'fn' + a, + u = 'fn' + s, + d = 'pushState', + l = 1e3, + f = 3e4; + }, + 3785: (e, t, r) => { + 'use strict'; + r.d(t, { R: () => c, b: () => u }); + var n = r(9908), + i = r(1863), + o = r(860), + a = r(8154), + s = r(993); + function c(e, t, r = {}, c = s.p_.INFO, u, d = (0, i.t)()) { + (0, n.p)(a.xV, ['API/logging/'.concat(c.toLowerCase(), '/called')], void 0, o.K7.metrics, e), + (0, n.p)(s.ET, [d, t, r, c, u], void 0, o.K7.logging, e); + } + function u(e) { + return 'string' == typeof e && Object.values(s.p_).some((t) => t === e.toUpperCase().trim()); + } + }, + 3878: (e, t, r) => { + 'use strict'; + function n(e, t) { + return { capture: e, passive: !1, signal: t }; + } + function i(e, t, r = !1, i) { + window.addEventListener(e, t, n(r, i)); + } + function o(e, t, r = !1, i) { + document.addEventListener(e, t, n(r, i)); + } + r.d(t, { DD: () => o, jT: () => n, sp: () => i }); + }, + 3962: (e, t, r) => { + 'use strict'; + r.d(t, { + AM: () => a, + O2: () => l, + OV: () => o, + Qu: () => f, + TZ: () => c, + ih: () => h, + pP: () => s, + t1: () => d, + tC: () => i, + wD: () => u, + }); + var n = r(860); + const i = ['click', 'keydown', 'submit'], + o = 'popstate', + a = 'api', + s = 'initialPageLoad', + c = n.K7.softNav, + u = 5e3, + d = 500, + l = { INITIAL_PAGE_LOAD: '', ROUTE_CHANGE: 1, UNSPECIFIED: 2 }, + f = { INTERACTION: 1, AJAX: 2, CUSTOM_END: 3, CUSTOM_TRACER: 4 }, + h = { IP: 'in progress', PF: 'pending finish', FIN: 'finished', CAN: 'cancelled' }; + }, + 4234: (e, t, r) => { + 'use strict'; + r.d(t, { W: () => o }); + var n = r(7836), + i = r(1687); + class o { + constructor(e, t) { + (this.agentIdentifier = e), (this.ee = n.ee.get(e)), (this.featureName = t), (this.blocked = !1); + } + deregisterDrain() { + (0, i.x3)(this.agentIdentifier, this.featureName); + } + } + }, + 4261: (e, t, r) => { + 'use strict'; + r.d(t, { + $9: () => d, + BL: () => c, + CH: () => p, + Dl: () => R, + Fw: () => w, + PA: () => v, + Pl: () => n, + Tb: () => f, + U2: () => a, + V1: () => E, + Wb: () => T, + bt: () => y, + cD: () => b, + d3: () => x, + dT: () => u, + eY: () => g, + fF: () => h, + hG: () => o, + hw: () => i, + k6: () => s, + nb: () => m, + o5: () => l, + }); + const n = 'api-', + i = n + 'ixn-', + o = 'addPageAction', + a = 'addToTrace', + s = 'addRelease', + c = 'finished', + u = 'interaction', + d = 'log', + l = 'noticeError', + f = 'pauseReplay', + h = 'recordCustomEvent', + p = 'recordReplay', + g = 'register', + m = 'setApplicationVersion', + v = 'setCurrentRouteName', + b = 'setCustomAttribute', + y = 'setErrorHandler', + w = 'setPageViewName', + R = 'setUserId', + x = 'start', + T = 'wrapLogger', + E = 'measure'; + }, + 5205: (e, t, r) => { + 'use strict'; + r.d(t, { j: () => O }); + var n = r(384), + i = r(1741); + var o = r(2555), + a = r(3333); + const s = (e) => { + if (!e || 'string' != typeof e) return !1; + try { + document.createDocumentFragment().querySelector(e); + } catch { + return !1; + } + return !0; + }; + var c = r(2614), + u = r(944), + d = r(8122); + const l = '[data-nr-mask]', + f = (e) => + (0, d.a)( + e, + (() => { + const e = { + feature_flags: [], + experimental: { resources: !1 }, + mask_selector: '*', + block_selector: '[data-nr-block]', + mask_input_options: { + color: !1, + date: !1, + 'datetime-local': !1, + email: !1, + month: !1, + number: !1, + range: !1, + search: !1, + tel: !1, + text: !1, + time: !1, + url: !1, + week: !1, + textarea: !1, + select: !1, + password: !0, + }, + }; + return { + ajax: { deny_list: void 0, block_internal: !0, enabled: !0, autoStart: !0 }, + api: { allow_registered_children: !0, duplicate_registered_data: !1 }, + distributed_tracing: { + enabled: void 0, + exclude_newrelic_header: void 0, + cors_use_newrelic_header: void 0, + cors_use_tracecontext_headers: void 0, + allowed_origins: void 0, + }, + get feature_flags() { + return e.feature_flags; + }, + set feature_flags(t) { + e.feature_flags = t; + }, + generic_events: { enabled: !0, autoStart: !0 }, + harvest: { interval: 30 }, + jserrors: { enabled: !0, autoStart: !0 }, + logging: { enabled: !0, autoStart: !0 }, + metrics: { enabled: !0, autoStart: !0 }, + obfuscate: void 0, + page_action: { enabled: !0 }, + page_view_event: { enabled: !0, autoStart: !0 }, + page_view_timing: { enabled: !0, autoStart: !0 }, + performance: { + capture_marks: !1, + capture_measures: !1, + capture_detail: !0, + resources: { + get enabled() { + return e.feature_flags.includes(a.$v.RESOURCES) || e.experimental.resources; + }, + set enabled(t) { + e.experimental.resources = t; + }, + asset_types: [], + first_party_domains: [], + ignore_newrelic: !0, + }, + }, + privacy: { cookies_enabled: !0 }, + proxy: { assets: void 0, beacon: void 0 }, + session: { expiresMs: c.wk, inactiveMs: c.BB }, + session_replay: { + autoStart: !0, + enabled: !1, + preload: !1, + sampling_rate: 10, + error_sampling_rate: 100, + collect_fonts: !1, + inline_images: !1, + fix_stylesheets: !0, + mask_all_inputs: !0, + get mask_text_selector() { + return e.mask_selector; + }, + set mask_text_selector(t) { + s(t) + ? (e.mask_selector = ''.concat(t, ',').concat(l)) + : '' === t || null === t + ? (e.mask_selector = l) + : (0, u.R)(5, t); + }, + get block_class() { + return 'nr-block'; + }, + get ignore_class() { + return 'nr-ignore'; + }, + get mask_text_class() { + return 'nr-mask'; + }, + get block_selector() { + return e.block_selector; + }, + set block_selector(t) { + s(t) ? (e.block_selector += ','.concat(t)) : '' !== t && (0, u.R)(6, t); + }, + get mask_input_options() { + return e.mask_input_options; + }, + set mask_input_options(t) { + t && 'object' == typeof t ? (e.mask_input_options = { ...t, password: !0 }) : (0, u.R)(7, t); + }, + }, + session_trace: { enabled: !0, autoStart: !0 }, + soft_navigations: { enabled: !0, autoStart: !0 }, + spa: { enabled: !0, autoStart: !0 }, + ssl: void 0, + user_actions: { enabled: !0, elementAttributes: ['id', 'className', 'tagName', 'type'] }, + }; + })() + ); + var h = r(6154), + p = r(9324); + let g = 0; + const m = { buildEnv: p.F3, distMethod: p.Xs, version: p.xv, originTime: h.WN }, + v = { + appMetadata: {}, + customTransaction: void 0, + denyList: void 0, + disabled: !1, + entityManager: void 0, + harvester: void 0, + isolatedBacklog: !1, + isRecording: !1, + loaderType: void 0, + maxBytes: 3e4, + obfuscator: void 0, + onerror: void 0, + ptid: void 0, + releaseIds: {}, + session: void 0, + timeKeeper: void 0, + jsAttributesMetadata: { bytes: 0 }, + get harvestCount() { + return ++g; + }, + }, + b = (e) => { + const t = (0, d.a)(e, v), + r = Object.keys(m).reduce( + (e, t) => ((e[t] = { value: m[t], writable: !1, configurable: !0, enumerable: !0 }), e), + {} + ); + return Object.defineProperties(t, r); + }; + var y = r(5701); + const w = (e) => { + const t = e.startsWith('http'); + (e += '/'), (r.p = t ? e : 'https://' + e); + }; + var R = r(7836), + x = r(3241); + const T = { + accountID: void 0, + trustKey: void 0, + agentID: void 0, + licenseKey: void 0, + applicationID: void 0, + xpid: void 0, + }, + E = (e) => (0, d.a)(e, T), + A = new Set(); + function O(e, t = {}, r, a) { + let { init: s, info: c, loader_config: u, runtime: d = {}, exposed: l = !0 } = t; + if (!c) { + const e = (0, n.pV)(); + (s = e.init), (c = e.info), (u = e.loader_config); + } + (e.init = f(s || {})), + (e.loader_config = E(u || {})), + (c.jsAttributes ??= {}), + h.bv && (c.jsAttributes.isWorker = !0), + (e.info = (0, o.D)(c)); + const p = e.init, + g = [c.beacon, c.errorBeacon]; + A.has(e.agentIdentifier) || + (p.proxy.assets && (w(p.proxy.assets), g.push(p.proxy.assets)), + p.proxy.beacon && g.push(p.proxy.beacon), + (e.beacons = [...g]), + (function (e) { + const t = (0, n.pV)(); + Object.getOwnPropertyNames(i.W.prototype).forEach((r) => { + const n = i.W.prototype[r]; + if ('function' != typeof n || 'constructor' === n) return; + let o = t[r]; + e[r] && + !1 !== e.exposed && + 'micro-agent' !== e.runtime?.loaderType && + (t[r] = (...t) => { + const n = e[r](...t); + return o ? o(...t) : n; + }); + }); + })(e), + (0, n.US)('activatedFeatures', y.B), + (e.runSoftNavOverSpa &&= !0 === p.soft_navigations.enabled && p.feature_flags.includes('soft_nav'))), + (d.denyList = [...(p.ajax.deny_list || []), ...(p.ajax.block_internal ? g : [])]), + (d.ptid = e.agentIdentifier), + (d.loaderType = r), + (e.runtime = b(d)), + A.has(e.agentIdentifier) || + ((e.ee = R.ee.get(e.agentIdentifier)), + (e.exposed = l), + (0, x.W)({ + agentIdentifier: e.agentIdentifier, + drained: !!y.B?.[e.agentIdentifier], + type: 'lifecycle', + name: 'initialize', + feature: void 0, + data: e.config, + })), + A.add(e.agentIdentifier); + } + }, + 5270: (e, t, r) => { + 'use strict'; + r.d(t, { Aw: () => a, SR: () => o, rF: () => s }); + var n = r(384), + i = r(7767); + function o(e) { + return !!(0, n.dV)().o.MO && (0, i.V)(e) && !0 === e?.session_trace.enabled; + } + function a(e) { + return !0 === e?.session_replay.preload && o(e); + } + function s(e, t) { + try { + if ('string' == typeof t?.type) { + if ('password' === t.type.toLowerCase()) return '*'.repeat(e?.length || 0); + if (void 0 !== t?.dataset?.nrUnmask || t?.classList?.contains('nr-unmask')) return e; + } + } catch (e) {} + return 'string' == typeof e ? e.replace(/[\S]/g, '*') : '*'.repeat(e?.length || 0); + } + }, + 5289: (e, t, r) => { + 'use strict'; + r.d(t, { GG: () => o, Qr: () => s, sB: () => a }); + var n = r(3878); + function i() { + return 'undefined' == typeof document || 'complete' === document.readyState; + } + function o(e, t) { + if (i()) return e(); + (0, n.sp)('load', e, t); + } + function a(e) { + if (i()) return e(); + (0, n.DD)('DOMContentLoaded', e); + } + function s(e) { + if (i()) return e(); + (0, n.sp)('popstate', e); + } + }, + 5607: (e, t, r) => { + 'use strict'; + r.d(t, { W: () => n }); + const n = (0, r(9566).bz)(); + }, + 5701: (e, t, r) => { + 'use strict'; + r.d(t, { B: () => o, t: () => a }); + var n = r(3241); + const i = new Set(), + o = {}; + function a(e, t) { + const r = t.agentIdentifier; + (o[r] ??= {}), + e && + 'object' == typeof e && + (i.has(r) || + (t.ee.emit('rumresp', [e]), + (o[r] = e), + i.add(r), + (0, n.W)({ + agentIdentifier: r, + loaded: !0, + drained: !0, + type: 'lifecycle', + name: 'load', + feature: void 0, + data: e, + }))); + } + }, + 6154: (e, t, r) => { + 'use strict'; + r.d(t, { + A4: () => s, + OF: () => d, + RI: () => i, + WN: () => h, + bv: () => o, + gm: () => a, + lR: () => f, + m: () => u, + mw: () => c, + sb: () => l, + }); + var n = r(1863); + const i = 'undefined' != typeof window && !!window.document, + o = + 'undefined' != typeof WorkerGlobalScope && + (('undefined' != typeof self && + self instanceof WorkerGlobalScope && + self.navigator instanceof WorkerNavigator) || + ('undefined' != typeof globalThis && + globalThis instanceof WorkerGlobalScope && + globalThis.navigator instanceof WorkerNavigator)), + a = i + ? window + : 'undefined' != typeof WorkerGlobalScope && + (('undefined' != typeof self && self instanceof WorkerGlobalScope && self) || + ('undefined' != typeof globalThis && globalThis instanceof WorkerGlobalScope && globalThis)), + s = 'complete' === a?.document?.readyState, + c = Boolean('hidden' === a?.document?.visibilityState), + u = '' + a?.location, + d = /iPad|iPhone|iPod/.test(a.navigator?.userAgent), + l = d && 'undefined' == typeof SharedWorker, + f = (() => { + const e = a.navigator?.userAgent?.match(/Firefox[/\s](\d+\.\d+)/); + return Array.isArray(e) && e.length >= 2 ? +e[1] : 0; + })(), + h = Date.now() - (0, n.t)(); + }, + 6344: (e, t, r) => { + 'use strict'; + r.d(t, { + BB: () => d, + G4: () => o, + Qb: () => l, + TZ: () => i, + Ug: () => a, + _s: () => s, + bc: () => u, + yP: () => c, + }); + var n = r(2614); + const i = r(860).K7.sessionReplay, + o = { RECORD: 'recordReplay', PAUSE: 'pauseReplay', ERROR_DURING_REPLAY: 'errorDuringReplay' }, + a = 0.12, + s = { DomContentLoaded: 0, Load: 1, FullSnapshot: 2, IncrementalSnapshot: 3, Meta: 4, Custom: 5 }, + c = { [n.g.ERROR]: 15e3, [n.g.FULL]: 3e5, [n.g.OFF]: 0 }, + u = { + RESET: { message: 'Session was reset', sm: 'Reset' }, + IMPORT: { message: 'Recorder failed to import', sm: 'Import' }, + TOO_MANY: { message: '429: Too Many Requests', sm: 'Too-Many' }, + TOO_BIG: { message: 'Payload was too large', sm: 'Too-Big' }, + CROSS_TAB: { message: 'Session Entity was set to OFF on another tab', sm: 'Cross-Tab' }, + ENTITLEMENTS: { message: 'Session Replay is not allowed and will not be started', sm: 'Entitlement' }, + }, + d = 5e3, + l = { + API: 'api', + RESUME: 'resume', + SWITCH_TO_FULL: 'switchToFull', + INITIALIZE: 'initialize', + PRELOAD: 'preload', + }; + }, + 6389: (e, t, r) => { + 'use strict'; + function n(e, t = 500, r = {}) { + const n = r?.leading || !1; + let i; + return (...r) => { + n && + void 0 === i && + (e.apply(this, r), + (i = setTimeout(() => { + i = clearTimeout(i); + }, t))), + n || + (clearTimeout(i), + (i = setTimeout(() => { + e.apply(this, r); + }, t))); + }; + } + function i(e) { + let t = !1; + return (...r) => { + t || ((t = !0), e.apply(this, r)); + }; + } + r.d(t, { J: () => i, s: () => n }); + }, + 6630: (e, t, r) => { + 'use strict'; + r.d(t, { T: () => n }); + const n = r(860).K7.pageViewEvent; + }, + 6774: (e, t, r) => { + 'use strict'; + r.d(t, { T: () => n }); + const n = r(860).K7.jserrors; + }, + 7295: (e, t, r) => { + 'use strict'; + r.d(t, { Xv: () => a, gX: () => i, iW: () => o }); + var n = []; + function i(e) { + if (!e || o(e)) return !1; + if (0 === n.length) return !0; + for (var t = 0; t < n.length; t++) { + var r = n[t]; + if ('*' === r.hostname) return !1; + if (s(r.hostname, e.hostname) && c(r.pathname, e.pathname)) return !1; + } + return !0; + } + function o(e) { + return void 0 === e.hostname; + } + function a(e) { + if (((n = []), e && e.length)) + for (var t = 0; t < e.length; t++) { + let r = e[t]; + if (!r) continue; + 0 === r.indexOf('http://') ? (r = r.substring(7)) : 0 === r.indexOf('https://') && (r = r.substring(8)); + const i = r.indexOf('/'); + let o, a; + i > 0 ? ((o = r.substring(0, i)), (a = r.substring(i))) : ((o = r), (a = '')); + let [s] = o.split(':'); + n.push({ hostname: s, pathname: a }); + } + } + function s(e, t) { + return !(e.length > t.length) && t.indexOf(e) === t.length - e.length; + } + function c(e, t) { + return ( + 0 === e.indexOf('/') && (e = e.substring(1)), + 0 === t.indexOf('/') && (t = t.substring(1)), + '' === e || e === t + ); + } + }, + 7378: (e, t, r) => { + 'use strict'; + r.d(t, { + $p: () => x, + BR: () => b, + Kp: () => R, + L3: () => y, + Lc: () => c, + NC: () => o, + SG: () => d, + TZ: () => i, + U6: () => p, + UT: () => m, + d3: () => w, + dT: () => f, + e5: () => E, + gx: () => v, + l9: () => l, + oW: () => h, + op: () => g, + rw: () => u, + tH: () => A, + uP: () => s, + wW: () => T, + xq: () => a, + }); + var n = r(384); + const i = r(860).K7.spa, + o = ['click', 'submit', 'keypress', 'keydown', 'keyup', 'change'], + a = 999, + s = 'fn-start', + c = 'fn-end', + u = 'cb-start', + d = 'api-ixn-', + l = 'remaining', + f = 'interaction', + h = 'spaNode', + p = 'jsonpNode', + g = 'fetch-start', + m = 'fetch-done', + v = 'fetch-body-', + b = 'jsonp-end', + y = (0, n.dV)().o.ST, + w = '-start', + R = '-end', + x = '-body', + T = 'cb' + R, + E = 'jsTime', + A = 'fetch'; + }, + 7485: (e, t, r) => { + 'use strict'; + r.d(t, { D: () => i }); + var n = r(6154); + function i(e) { + if (0 === (e || '').indexOf('data:')) return { protocol: 'data' }; + try { + const t = new URL(e, location.href), + r = { + port: t.port, + hostname: t.hostname, + pathname: t.pathname, + search: t.search, + protocol: t.protocol.slice(0, t.protocol.indexOf(':')), + sameOrigin: t.protocol === n.gm?.location?.protocol && t.host === n.gm?.location?.host, + }; + return ( + (r.port && '' !== r.port) || + ('http:' === t.protocol && (r.port = '80'), 'https:' === t.protocol && (r.port = '443')), + r.pathname && '' !== r.pathname + ? r.pathname.startsWith('/') || (r.pathname = '/'.concat(r.pathname)) + : (r.pathname = '/'), + r + ); + } catch (e) { + return {}; + } + } + }, + 7699: (e, t, r) => { + 'use strict'; + r.d(t, { It: () => i, No: () => n, qh: () => a, uh: () => o }); + const n = 16e3, + i = 1e6, + o = 'NR_CONTAINER_AGENT', + a = 'SESSION_ERROR'; + }, + 7767: (e, t, r) => { + 'use strict'; + r.d(t, { V: () => i }); + var n = r(6154); + const i = (e) => n.RI && !0 === e?.privacy.cookies_enabled; + }, + 7836: (e, t, r) => { + 'use strict'; + r.d(t, { P: () => s, ee: () => c }); + var n = r(384), + i = r(8990), + o = r(2646), + a = r(5607); + const s = 'nr@context:'.concat(a.W), + c = (function e(t, r) { + var n = {}, + a = {}, + d = {}, + l = !1; + try { + l = 16 === r.length && u.initializedAgents?.[r]?.runtime.isolatedBacklog; + } catch (e) {} + var f = { + on: p, + addEventListener: p, + removeEventListener: function (e, t) { + var r = n[e]; + if (!r) return; + for (var i = 0; i < r.length; i++) r[i] === t && r.splice(i, 1); + }, + emit: function (e, r, n, i, o) { + !1 !== o && (o = !0); + if (c.aborted && !i) return; + t && o && t.emit(e, r, n); + var s = h(n); + g(e).forEach((e) => { + e.apply(s, r); + }); + var u = v()[a[e]]; + u && u.push([f, e, r, s]); + return s; + }, + get: m, + listeners: g, + context: h, + buffer: function (e, t) { + const r = v(); + if (((t = t || 'feature'), f.aborted)) return; + Object.entries(e || {}).forEach(([e, n]) => { + (a[n] = t), t in r || (r[t] = []); + }); + }, + abort: function () { + (f._aborted = !0), + Object.keys(f.backlog).forEach((e) => { + delete f.backlog[e]; + }); + }, + isBuffering: function (e) { + return !!v()[a[e]]; + }, + debugId: r, + backlog: l ? {} : t && 'object' == typeof t.backlog ? t.backlog : {}, + isolatedBacklog: l, + }; + return ( + Object.defineProperty(f, 'aborted', { + get: () => { + let e = f._aborted || !1; + return e || (t && (e = t.aborted), e); + }, + }), + f + ); + function h(e) { + return e && e instanceof o.y ? e : e ? (0, i.I)(e, s, () => new o.y(s)) : new o.y(s); + } + function p(e, t) { + n[e] = g(e).concat(t); + } + function g(e) { + return n[e] || []; + } + function m(t) { + return (d[t] = d[t] || e(f, t)); + } + function v() { + return f.backlog; + } + })(void 0, 'globalEE'), + u = (0, n.Zm)(); + u.ee || (u.ee = c); + }, + 8122: (e, t, r) => { + 'use strict'; + r.d(t, { a: () => i }); + var n = r(944); + function i(e, t) { + try { + if (!e || 'object' != typeof e) return (0, n.R)(3); + if (!t || 'object' != typeof t) return (0, n.R)(4); + const r = Object.create(Object.getPrototypeOf(t), Object.getOwnPropertyDescriptors(t)), + o = 0 === Object.keys(r).length ? e : r; + for (let a in o) + if (void 0 !== e[a]) + try { + if (null === e[a]) { + r[a] = null; + continue; + } + Array.isArray(e[a]) && Array.isArray(t[a]) + ? (r[a] = Array.from(new Set([...e[a], ...t[a]]))) + : 'object' == typeof e[a] && 'object' == typeof t[a] + ? (r[a] = i(e[a], t[a])) + : (r[a] = e[a]); + } catch (e) { + r[a] || (0, n.R)(1, e); + } + return r; + } catch (e) { + (0, n.R)(2, e); + } + } + }, + 8139: (e, t, r) => { + 'use strict'; + r.d(t, { u: () => f }); + var n = r(7836), + i = r(3434), + o = r(8990), + a = r(6154); + const s = {}, + c = a.gm.XMLHttpRequest, + u = 'addEventListener', + d = 'removeEventListener', + l = 'nr@wrapped:'.concat(n.P); + function f(e) { + var t = (function (e) { + return (e || n.ee).get('events'); + })(e); + if (s[t.debugId]++) return t; + s[t.debugId] = 1; + var r = (0, i.YM)(t, !0); + function f(e) { + r.inPlace(e, [u, d], '-', p); + } + function p(e, t) { + return e[1]; + } + return ( + 'getPrototypeOf' in Object && (a.RI && h(document, f), c && h(c.prototype, f), h(a.gm, f)), + t.on(u + '-start', function (e, t) { + var n = e[1]; + if (null !== n && ('function' == typeof n || 'object' == typeof n) && 'newrelic' !== e[0]) { + var i = (0, o.I)(n, l, function () { + var e = { + object: function () { + if ('function' != typeof n.handleEvent) return; + return n.handleEvent.apply(n, arguments); + }, + function: n, + }[typeof n]; + return e ? r(e, 'fn-', null, e.name || 'anonymous') : n; + }); + this.wrapped = e[1] = i; + } + }), + t.on(d + '-start', function (e) { + e[1] = this.wrapped || e[1]; + }), + t + ); + } + function h(e, t, ...r) { + let n = e; + for (; 'object' == typeof n && !Object.prototype.hasOwnProperty.call(n, u); ) n = Object.getPrototypeOf(n); + n && t(n, ...r); + } + }, + 8154: (e, t, r) => { + 'use strict'; + r.d(t, { z_: () => o, XG: () => s, TZ: () => n, rs: () => i, xV: () => a }); + r(6154), r(9566), r(384); + const n = r(860).K7.metrics, + i = 'sm', + o = 'cm', + a = 'storeSupportabilityMetrics', + s = 'storeEventMetrics'; + }, + 8374: (e, t, r) => { + r.nc = (() => { + try { + return document?.currentScript?.nonce; + } catch (e) {} + return ''; + })(); + }, + 8990: (e, t, r) => { + 'use strict'; + r.d(t, { I: () => i }); + var n = Object.prototype.hasOwnProperty; + function i(e, t, r) { + if (n.call(e, t)) return e[t]; + var i = r(); + if (Object.defineProperty && Object.keys) + try { + return Object.defineProperty(e, t, { value: i, writable: !0, enumerable: !1 }), i; + } catch (e) {} + return (e[t] = i), i; + } + }, + 9300: (e, t, r) => { + 'use strict'; + r.d(t, { T: () => n }); + const n = r(860).K7.ajax; + }, + 9324: (e, t, r) => { + 'use strict'; + r.d(t, { AJ: () => a, F3: () => i, Xs: () => o, Yq: () => s, xv: () => n }); + const n = '1.301.0', + i = 'PROD', + o = 'CDN', + a = '@newrelic/rrweb', + s = '1.0.1'; + }, + 9566: (e, t, r) => { + 'use strict'; + r.d(t, { LA: () => s, ZF: () => c, bz: () => a, el: () => u }); + var n = r(6154); + const i = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'; + function o(e, t) { + return e ? 15 & e[t] : (16 * Math.random()) | 0; + } + function a() { + const e = n.gm?.crypto || n.gm?.msCrypto; + let t, + r = 0; + return ( + e && e.getRandomValues && (t = e.getRandomValues(new Uint8Array(30))), + i + .split('') + .map((e) => ('x' === e ? o(t, r++).toString(16) : 'y' === e ? ((3 & o()) | 8).toString(16) : e)) + .join('') + ); + } + function s(e) { + const t = n.gm?.crypto || n.gm?.msCrypto; + let r, + i = 0; + t && t.getRandomValues && (r = t.getRandomValues(new Uint8Array(e))); + const a = []; + for (var s = 0; s < e; s++) a.push(o(r, i++).toString(16)); + return a.join(''); + } + function c() { + return s(16); + } + function u() { + return s(32); + } + }, + 9908: (e, t, r) => { + 'use strict'; + r.d(t, { d: () => n, p: () => i }); + var n = r(7836).ee.get('handle'); + function i(e, t, r, i, o) { + o ? (o.buffer([e], i), o.emit(e, t, r)) : (n.buffer([e], i), n.emit(e, t, r)); + } + }, + }, + n = {}; + function i(e) { + var t = n[e]; + if (void 0 !== t) return t.exports; + var o = (n[e] = { exports: {} }); + return r[e](o, o.exports, i), o.exports; + } + (i.m = r), + (i.d = (e, t) => { + for (var r in t) i.o(t, r) && !i.o(e, r) && Object.defineProperty(e, r, { enumerable: !0, get: t[r] }); + }), + (i.f = {}), + (i.e = (e) => Promise.all(Object.keys(i.f).reduce((t, r) => (i.f[r](e, t), t), []))), + (i.u = (e) => ({ 212: 'nr-spa-compressor', 249: 'nr-spa-recorder', 478: 'nr-spa' })[e] + '-1.301.0.min.js'), + (i.o = (e, t) => Object.prototype.hasOwnProperty.call(e, t)), + (e = {}), + (t = 'NRBA-1.301.0.PROD:'), + (i.l = (r, n, o, a) => { + if (e[r]) e[r].push(n); + else { + var s, c; + if (void 0 !== o) + for (var u = document.getElementsByTagName('script'), d = 0; d < u.length; d++) { + var l = u[d]; + if (l.getAttribute('src') == r || l.getAttribute('data-webpack') == t + o) { + s = l; + break; + } + } + if (!s) { + c = !0; + var f = { + 478: 'sha512-7qHClSVBtoyiwRvCkgyaF5Ps0RAENoPwjauK0I+0bYyBYefdZVshGSe8JQWh/Wexd7hFuUui5xp+2xn5U1a3ZA==', + 249: 'sha512-KuEP0gQ0mAldYT/AqNp3NW4kVb0kLtfIlXaPSFq4WQRFf8vKVNnSqiafso/bWR75halLwFsgmJtdLJEzntVZoQ==', + 212: 'sha512-fqWEILwVJyfYV9/SedvSjCZ6hDRNjOvwYfN73wxZtahaztcFZ2cr3Ns172tBGIDQeWO25QmSlihZm+awv8ma/w==', + }; + ((s = document.createElement('script')).charset = 'utf-8'), + i.nc && s.setAttribute('nonce', i.nc), + s.setAttribute('data-webpack', t + o), + (s.src = r), + 0 !== s.src.indexOf(window.location.origin + '/') && (s.crossOrigin = 'anonymous'), + f[a] && (s.integrity = f[a]); + } + e[r] = [n]; + var h = (t, n) => { + (s.onerror = s.onload = null), clearTimeout(p); + var i = e[r]; + if ((delete e[r], s.parentNode && s.parentNode.removeChild(s), i && i.forEach((e) => e(n)), t)) return t(n); + }, + p = setTimeout(h.bind(null, void 0, { type: 'timeout', target: s }), 12e4); + (s.onerror = h.bind(null, s.onerror)), (s.onload = h.bind(null, s.onload)), c && document.head.appendChild(s); + } + }), + (i.r = (e) => { + 'undefined' != typeof Symbol && + Symbol.toStringTag && + Object.defineProperty(e, Symbol.toStringTag, { value: 'Module' }), + Object.defineProperty(e, '__esModule', { value: !0 }); + }), + (i.p = 'https://js-agent.newrelic.com/'), + (() => { + var e = { 38: 0, 788: 0 }; + i.f.j = (t, r) => { + var n = i.o(e, t) ? e[t] : void 0; + if (0 !== n) + if (n) r.push(n[2]); + else { + var o = new Promise((r, i) => (n = e[t] = [r, i])); + r.push((n[2] = o)); + var a = i.p + i.u(t), + s = new Error(); + i.l( + a, + (r) => { + if (i.o(e, t) && (0 !== (n = e[t]) && (e[t] = void 0), n)) { + var o = r && ('load' === r.type ? 'missing' : r.type), + a = r && r.target && r.target.src; + (s.message = 'Loading chunk ' + t + ' failed.\n(' + o + ': ' + a + ')'), + (s.name = 'ChunkLoadError'), + (s.type = o), + (s.request = a), + n[1](s); + } + }, + 'chunk-' + t, + t + ); + } + }; + var t = (t, r) => { + var n, + o, + [a, s, c] = r, + u = 0; + if (a.some((t) => 0 !== e[t])) { + for (n in s) i.o(s, n) && (i.m[n] = s[n]); + if (c) c(i); + } + for (t && t(r); u < a.length; u++) (o = a[u]), i.o(e, o) && e[o] && e[o][0](), (e[o] = 0); + }, + r = (self['webpackChunk:NRBA-1.301.0.PROD'] = self['webpackChunk:NRBA-1.301.0.PROD'] || []); + r.forEach(t.bind(null, 0)), (r.push = t.bind(null, r.push.bind(r))); + })(), + (() => { + 'use strict'; + i(8374); + var e = i(9566), + t = i(1741); + class r extends t.W { + agentIdentifier = (0, e.LA)(16); + } + var n = i(860); + const o = Object.values(n.K7); + var a = i(5205); + var s = i(9908), + c = i(1863), + u = i(4261), + d = i(3241), + l = i(944), + f = i(5701), + h = i(8154); + function p(e, t, i, o) { + const a = o || i; + !a || + (a[e] && a[e] !== r.prototype[e]) || + (a[e] = function () { + (0, s.p)(h.xV, ['API/' + e + '/called'], void 0, n.K7.metrics, i.ee), + (0, d.W)({ + agentIdentifier: i.agentIdentifier, + drained: !!f.B?.[i.agentIdentifier], + type: 'data', + name: 'api', + feature: u.Pl + e, + data: {}, + }); + try { + return t.apply(this, arguments); + } catch (e) { + (0, l.R)(23, e); + } + }); + } + function g(e, t, r, n, i) { + const o = e.info; + null === r ? delete o.jsAttributes[t] : (o.jsAttributes[t] = r), + (i || null === r) && (0, s.p)(u.Pl + n, [(0, c.t)(), t, r], void 0, 'session', e.ee); + } + var m = i(1687), + v = i(4234), + b = i(5289), + y = i(6154), + w = i(5270), + R = i(7767), + x = i(6389), + T = i(7699); + class E extends v.W { + constructor(e, t) { + super(e.agentIdentifier, t), + (this.agentRef = e), + (this.abortHandler = void 0), + (this.featAggregate = void 0), + (this.onAggregateImported = void 0), + (this.deferred = Promise.resolve()), + !1 === e.init[this.featureName].autoStart + ? (this.deferred = new Promise((t, r) => { + this.ee.on( + 'manual-start-all', + (0, x.J)(() => { + (0, m.Ak)(e.agentIdentifier, this.featureName), t(); + }) + ); + })) + : (0, m.Ak)(e.agentIdentifier, t); + } + importAggregator(e, t, r = {}) { + if (this.featAggregate) return; + let n; + this.onAggregateImported = new Promise((e) => { + n = e; + }); + const o = async () => { + let o; + await this.deferred; + try { + if ((0, R.V)(e.init)) { + const { setupAgentSession: t } = await i.e(478).then(i.bind(i, 8766)); + o = t(e); + } + } catch (e) { + (0, l.R)(20, e), + this.ee.emit('internal-error', [e]), + (0, s.p)(T.qh, [e], void 0, this.featureName, this.ee); + } + try { + if (!this.#t(this.featureName, o, e.init)) + return (0, m.Ze)(this.agentIdentifier, this.featureName), void n(!1); + const { Aggregate: i } = await t(); + (this.featAggregate = new i(e, r)), + e.runtime.harvester.initializedAggregates.push(this.featAggregate), + n(!0); + } catch (e) { + (0, l.R)(34, e), + this.abortHandler?.(), + (0, m.Ze)(this.agentIdentifier, this.featureName, !0), + n(!1), + this.ee && this.ee.abort(); + } + }; + y.RI ? (0, b.GG)(() => o(), !0) : o(); + } + #t(e, t, r) { + if (this.blocked) return !1; + switch (e) { + case n.K7.sessionReplay: + return (0, w.SR)(r) && !!t; + case n.K7.sessionTrace: + return !!t; + default: + return !0; + } + } + } + var A = i(6630), + O = i(2614); + class S extends E { + static featureName = A.T; + constructor(e) { + var t; + super(e, A.T), + this.setupInspectionEvents(e.agentIdentifier), + (t = e), + p( + u.Fw, + function (e, r) { + 'string' == typeof e && + ('/' !== e.charAt(0) && (e = '/' + e), + (t.runtime.customTransaction = (r || 'http://custom.transaction') + e), + (0, s.p)(u.Pl + u.Fw, [(0, c.t)()], void 0, void 0, t.ee)); + }, + t + ), + this.ee.on('api-send-rum', (e, t) => (0, s.p)('send-rum', [e, t], void 0, this.featureName, this.ee)), + this.importAggregator(e, () => i.e(478).then(i.bind(i, 1983))); + } + setupInspectionEvents(e) { + const t = (t, r) => { + t && + (0, d.W)({ + agentIdentifier: e, + timeStamp: t.timeStamp, + loaded: 'complete' === t.target.readyState, + type: 'window', + name: r, + data: t.target.location + '', + }); + }; + (0, b.sB)((e) => { + t(e, 'DOMContentLoaded'); + }), + (0, b.GG)((e) => { + t(e, 'load'); + }), + (0, b.Qr)((e) => { + t(e, 'navigate'); + }), + this.ee.on(O.tS.UPDATE, (t, r) => { + (0, d.W)({ agentIdentifier: e, type: 'lifecycle', name: 'session', data: r }); + }); + } + } + var N = i(384); + var _ = i(2843), + I = i(3878), + P = i(782); + class j extends E { + static featureName = P.T; + constructor(e) { + super(e, P.T), + y.RI && + ((0, _.u)(() => (0, s.p)('docHidden', [(0, c.t)()], void 0, P.T, this.ee), !0), + (0, I.sp)('pagehide', () => (0, s.p)('winPagehide', [(0, c.t)()], void 0, P.T, this.ee)), + this.importAggregator(e, () => i.e(478).then(i.bind(i, 9917)))); + } + } + class C extends E { + static featureName = h.TZ; + constructor(e) { + super(e, h.TZ), + y.RI && + document.addEventListener('securitypolicyviolation', (e) => { + (0, s.p)(h.xV, ['Generic/CSPViolation/Detected'], void 0, this.featureName, this.ee); + }), + this.importAggregator(e, () => i.e(478).then(i.bind(i, 6555))); + } + } + var k = i(6774), + L = i(3304); + class H { + constructor(e, t, r, n, i) { + (this.name = 'UncaughtError'), + (this.message = 'string' == typeof e ? e : (0, L.A)(e)), + (this.sourceURL = t), + (this.line = r), + (this.column = n), + (this.__newrelic = i); + } + } + function M(e) { + return U(e) + ? e + : new H( + void 0 !== e?.message ? e.message : e, + e?.filename || e?.sourceURL, + e?.lineno || e?.line, + e?.colno || e?.col, + e?.__newrelic, + e?.cause + ); + } + function D(e) { + const t = 'Unhandled Promise Rejection: '; + if (!e?.reason) return; + if (U(e.reason)) { + try { + e.reason.message.startsWith(t) || (e.reason.message = t + e.reason.message); + } catch (e) {} + return M(e.reason); + } + const r = M(e.reason); + return (r.message || '').startsWith(t) || (r.message = t + r.message), r; + } + function K(e) { + if (e.error instanceof SyntaxError && !/:\d+$/.test(e.error.stack?.trim())) { + const t = new H(e.message, e.filename, e.lineno, e.colno, e.error.__newrelic, e.cause); + return (t.name = SyntaxError.name), t; + } + return U(e.error) ? e.error : M(e); + } + function U(e) { + return e instanceof Error && !!e.stack; + } + function F(e, t, r, i, o = (0, c.t)()) { + 'string' == typeof e && (e = new Error(e)), + (0, s.p)('err', [e, o, !1, t, r.runtime.isRecording, void 0, i], void 0, n.K7.jserrors, r.ee), + (0, s.p)('uaErr', [], void 0, n.K7.genericEvents, r.ee); + } + var W = i(3496), + B = i(993), + G = i(3785); + function V(e, { customAttributes: t = {}, level: r = B.p_.INFO } = {}, n, i, o = (0, c.t)()) { + (0, G.R)(n.ee, e, t, r, i, o); + } + function z(e, t, r, i, o = (0, c.t)()) { + (0, s.p)(u.Pl + u.hG, [o, e, t, i], void 0, n.K7.genericEvents, r.ee); + } + function Z(e) { + p( + u.eY, + function (t) { + return (function (e, t) { + const r = {}; + let i, o; + (0, l.R)(54, 'newrelic.register'), e.init.api.allow_registered_children || (i = () => (0, l.R)(55)); + (t && (0, W.I)(t)) || (i = () => (0, l.R)(48, t)); + const a = { + addPageAction: (n, i = {}) => { + u(z, [n, { ...r, ...i }, e], t); + }, + log: (n, i = {}) => { + u(V, [n, { ...i, customAttributes: { ...r, ...(i.customAttributes || {}) } }, e], t); + }, + noticeError: (n, i = {}) => { + u(F, [n, { ...r, ...i }, e], t); + }, + setApplicationVersion: (e) => { + r['application.version'] = e; + }, + setCustomAttribute: (e, t) => { + r[e] = t; + }, + setUserId: (e) => { + r['enduser.id'] = e; + }, + metadata: { + customAttributes: r, + target: t, + get connected() { + return o || Promise.reject(new Error('Failed to connect')); + }, + }, + }; + i + ? i() + : (o = new Promise((n, i) => { + try { + const o = e.runtime?.entityManager; + let s = !!o?.get().entityGuid, + c = o?.getEntityGuidFor(t.licenseKey, t.applicationID), + u = !!c; + if (s && u) (t.entityGuid = c), n(a); + else { + const d = setTimeout(() => i(new Error('Failed to connect - Timeout')), 15e3); + function l(r) { + (0, W.A)(r, e) + ? (s ||= !0) + : t.licenseKey === r.licenseKey && + t.applicationID === r.applicationID && + ((u = !0), (t.entityGuid = r.entityGuid)), + s && u && (clearTimeout(d), e.ee.removeEventListener('entity-added', l), n(a)); + } + e.ee.emit('api-send-rum', [r, t]), e.ee.on('entity-added', l); + } + } catch (f) { + i(f); + } + })); + const u = async (t, r, a) => { + if (i) return i(); + const u = (0, c.t)(); + (0, s.p)(h.xV, ['API/register/'.concat(t.name, '/called')], void 0, n.K7.metrics, e.ee); + try { + await o; + const n = e.init.api.duplicate_registered_data; + (!0 === n || (Array.isArray(n) && n.includes(a.entityGuid))) && t(...r, void 0, u), + t(...r, a.entityGuid, u); + } catch (e) { + (0, l.R)(50, e); + } + }; + return a; + })(e, t); + }, + e + ); + } + class q extends E { + static featureName = k.T; + constructor(e) { + var t; + super(e, k.T), + (t = e), + p(u.o5, (e, r) => F(e, r, t), t), + (function (e) { + p( + u.bt, + function (t) { + e.runtime.onerror = t; + }, + e + ); + })(e), + (function (e) { + let t = 0; + p( + u.k6, + function (e, r) { + ++t > 10 || (this.runtime.releaseIds[e.slice(-200)] = ('' + r).slice(-200)); + }, + e + ); + })(e), + Z(e); + try { + this.removeOnAbort = new AbortController(); + } catch (e) {} + this.ee.on('internal-error', (t, r) => { + this.abortHandler && + (0, s.p)('ierr', [M(t), (0, c.t)(), !0, {}, e.runtime.isRecording, r], void 0, this.featureName, this.ee); + }), + y.gm.addEventListener( + 'unhandledrejection', + (t) => { + this.abortHandler && + (0, s.p)( + 'err', + [D(t), (0, c.t)(), !1, { unhandledPromiseRejection: 1 }, e.runtime.isRecording], + void 0, + this.featureName, + this.ee + ); + }, + (0, I.jT)(!1, this.removeOnAbort?.signal) + ), + y.gm.addEventListener( + 'error', + (t) => { + this.abortHandler && + (0, s.p)('err', [K(t), (0, c.t)(), !1, {}, e.runtime.isRecording], void 0, this.featureName, this.ee); + }, + (0, I.jT)(!1, this.removeOnAbort?.signal) + ), + (this.abortHandler = this.#r), + this.importAggregator(e, () => i.e(478).then(i.bind(i, 2176))); + } + #r() { + this.removeOnAbort?.abort(), (this.abortHandler = void 0); + } + } + var X = i(8990); + let Y = 1; + function Q(e) { + const t = typeof e; + return !e || ('object' !== t && 'function' !== t) + ? -1 + : e === y.gm + ? 0 + : (0, X.I)(e, 'nr@id', function () { + return Y++; + }); + } + function J(e) { + if ('string' == typeof e && e.length) return e.length; + if ('object' == typeof e) { + if ('undefined' != typeof ArrayBuffer && e instanceof ArrayBuffer && e.byteLength) return e.byteLength; + if ('undefined' != typeof Blob && e instanceof Blob && e.size) return e.size; + if (!('undefined' != typeof FormData && e instanceof FormData)) + try { + return (0, L.A)(e).length; + } catch (e) { + return; + } + } + } + var ee = i(8139), + te = i(7836), + re = i(3434); + const ne = {}, + ie = ['open', 'send']; + function oe(e) { + var t = e || te.ee; + const r = (function (e) { + return (e || te.ee).get('xhr'); + })(t); + if (void 0 === y.gm.XMLHttpRequest) return r; + if (ne[r.debugId]++) return r; + (ne[r.debugId] = 1), (0, ee.u)(t); + var n = (0, re.YM)(r), + i = y.gm.XMLHttpRequest, + o = y.gm.MutationObserver, + a = y.gm.Promise, + s = y.gm.setInterval, + c = 'readystatechange', + u = ['onload', 'onerror', 'onabort', 'onloadstart', 'onloadend', 'onprogress', 'ontimeout'], + d = [], + f = (y.gm.XMLHttpRequest = function (e) { + const t = new i(e), + o = r.context(t); + try { + r.emit('new-xhr', [t], o), + t.addEventListener( + c, + ((a = o), + function () { + var e = this; + e.readyState > 3 && !a.resolved && ((a.resolved = !0), r.emit('xhr-resolved', [], e)), + n.inPlace(e, u, 'fn-', b); + }), + (0, I.jT)(!1) + ); + } catch (e) { + (0, l.R)(15, e); + try { + r.emit('internal-error', [e]); + } catch (e) {} + } + var a; + return t; + }); + function h(e, t) { + n.inPlace(t, ['onreadystatechange'], 'fn-', b); + } + if ( + ((function (e, t) { + for (var r in e) t[r] = e[r]; + })(i, f), + (f.prototype = i.prototype), + n.inPlace(f.prototype, ie, '-xhr-', b), + r.on('send-xhr-start', function (e, t) { + h(e, t), + (function (e) { + d.push(e), o && (p ? p.then(v) : s ? s(v) : ((g = -g), (m.data = g))); + })(t); + }), + r.on('open-xhr-start', h), + o) + ) { + var p = a && a.resolve(); + if (!s && !a) { + var g = 1, + m = document.createTextNode(g); + new o(v).observe(m, { characterData: !0 }); + } + } else + t.on('fn-end', function (e) { + (e[0] && e[0].type === c) || v(); + }); + function v() { + for (var e = 0; e < d.length; e++) h(0, d[e]); + d.length && (d = []); + } + function b(e, t) { + return t; + } + return r; + } + var ae = 'fetch-', + se = ae + 'body-', + ce = ['arrayBuffer', 'blob', 'json', 'text', 'formData'], + ue = y.gm.Request, + de = y.gm.Response, + le = 'prototype'; + const fe = {}; + function he(e) { + const t = (function (e) { + return (e || te.ee).get('fetch'); + })(e); + if (!(ue && de && y.gm.fetch)) return t; + if (fe[t.debugId]++) return t; + function r(e, r, n) { + var i = e[r]; + 'function' == typeof i && + (e[r] = function () { + var e, + r = [...arguments], + o = {}; + t.emit(n + 'before-start', [r], o), o[te.P] && o[te.P].dt && (e = o[te.P].dt); + var a = i.apply(this, r); + return ( + t.emit(n + 'start', [r, e], a), + a.then( + function (e) { + return t.emit(n + 'end', [null, e], a), e; + }, + function (e) { + throw (t.emit(n + 'end', [e], a), e); + } + ) + ); + }); + } + return ( + (fe[t.debugId] = 1), + ce.forEach((e) => { + r(ue[le], e, se), r(de[le], e, se); + }), + r(y.gm, 'fetch', ae), + t.on(ae + 'end', function (e, r) { + var n = this; + if (r) { + var i = r.headers.get('content-length'); + null !== i && (n.rxSize = i), t.emit(ae + 'done', [null, r], n); + } else t.emit(ae + 'done', [e], n); + }), + t + ); + } + var pe = i(7485); + class ge { + constructor(e) { + this.agentRef = e; + } + generateTracePayload(t) { + const r = this.agentRef.loader_config; + if (!this.shouldGenerateTrace(t) || !r) return null; + var n = (r.accountID || '').toString() || null, + i = (r.agentID || '').toString() || null, + o = (r.trustKey || '').toString() || null; + if (!n || !i) return null; + var a = (0, e.ZF)(), + s = (0, e.el)(), + c = Date.now(), + u = { spanId: a, traceId: s, timestamp: c }; + return ( + (t.sameOrigin || (this.isAllowedOrigin(t) && this.useTraceContextHeadersForCors())) && + ((u.traceContextParentHeader = this.generateTraceContextParentHeader(a, s)), + (u.traceContextStateHeader = this.generateTraceContextStateHeader(a, c, n, i, o))), + ((t.sameOrigin && !this.excludeNewrelicHeader()) || + (!t.sameOrigin && this.isAllowedOrigin(t) && this.useNewrelicHeaderForCors())) && + (u.newrelicHeader = this.generateTraceHeader(a, s, c, n, i, o)), + u + ); + } + generateTraceContextParentHeader(e, t) { + return '00-' + t + '-' + e + '-01'; + } + generateTraceContextStateHeader(e, t, r, n, i) { + return i + '@nr=0-1-' + r + '-' + n + '-' + e + '----' + t; + } + generateTraceHeader(e, t, r, n, i, o) { + if (!('function' == typeof y.gm?.btoa)) return null; + var a = { v: [0, 1], d: { ty: 'Browser', ac: n, ap: i, id: e, tr: t, ti: r } }; + return o && n !== o && (a.d.tk = o), btoa((0, L.A)(a)); + } + shouldGenerateTrace(e) { + return this.agentRef.init?.distributed_tracing?.enabled && this.isAllowedOrigin(e); + } + isAllowedOrigin(e) { + var t = !1; + const r = this.agentRef.init?.distributed_tracing; + if (e.sameOrigin) t = !0; + else if (r?.allowed_origins instanceof Array) + for (var n = 0; n < r.allowed_origins.length; n++) { + var i = (0, pe.D)(r.allowed_origins[n]); + if (e.hostname === i.hostname && e.protocol === i.protocol && e.port === i.port) { + t = !0; + break; + } + } + return t; + } + excludeNewrelicHeader() { + var e = this.agentRef.init?.distributed_tracing; + return !!e && !!e.exclude_newrelic_header; + } + useNewrelicHeaderForCors() { + var e = this.agentRef.init?.distributed_tracing; + return !!e && !1 !== e.cors_use_newrelic_header; + } + useTraceContextHeadersForCors() { + var e = this.agentRef.init?.distributed_tracing; + return !!e && !!e.cors_use_tracecontext_headers; + } + } + var me = i(9300), + ve = i(7295); + function be(e) { + return 'string' == typeof e + ? e + : e instanceof (0, N.dV)().o.REQ + ? e.url + : y.gm?.URL && e instanceof URL + ? e.href + : void 0; + } + var ye = ['load', 'error', 'abort', 'timeout'], + we = ye.length, + Re = (0, N.dV)().o.REQ, + xe = (0, N.dV)().o.XHR; + const Te = 'X-NewRelic-App-Data'; + class Ee extends E { + static featureName = me.T; + constructor(e) { + super(e, me.T), (this.dt = new ge(e)), (this.handler = (e, t, r, n) => (0, s.p)(e, t, r, n, this.ee)); + try { + const e = { xmlhttprequest: 'xhr', fetch: 'fetch', beacon: 'beacon' }; + y.gm?.performance?.getEntriesByType('resource').forEach((t) => { + if (t.initiatorType in e && 0 !== t.responseStatus) { + const r = { status: t.responseStatus }, + i = { rxSize: t.transferSize, duration: Math.floor(t.duration), cbTime: 0 }; + Ae(r, t.name), + this.handler('xhr', [r, i, t.startTime, t.responseEnd, e[t.initiatorType]], void 0, n.K7.ajax); + } + }); + } catch (e) {} + he(this.ee), + oe(this.ee), + (function (e, t, r, i) { + function o(e) { + var t = this; + (t.totalCbs = 0), + (t.called = 0), + (t.cbTime = 0), + (t.end = E), + (t.ended = !1), + (t.xhrGuids = {}), + (t.lastSize = null), + (t.loadCaptureCalled = !1), + (t.params = this.params || {}), + (t.metrics = this.metrics || {}), + (t.latestLongtaskEnd = 0), + e.addEventListener( + 'load', + function (r) { + A(t, e); + }, + (0, I.jT)(!1) + ), + y.lR || + e.addEventListener( + 'progress', + function (e) { + t.lastSize = e.loaded; + }, + (0, I.jT)(!1) + ); + } + function a(e) { + (this.params = { method: e[0] }), Ae(this, e[1]), (this.metrics = {}); + } + function u(t, r) { + e.loader_config.xpid && this.sameOrigin && r.setRequestHeader('X-NewRelic-ID', e.loader_config.xpid); + var n = i.generateTracePayload(this.parsedOrigin); + if (n) { + var o = !1; + n.newrelicHeader && (r.setRequestHeader('newrelic', n.newrelicHeader), (o = !0)), + n.traceContextParentHeader && + (r.setRequestHeader('traceparent', n.traceContextParentHeader), + n.traceContextStateHeader && r.setRequestHeader('tracestate', n.traceContextStateHeader), + (o = !0)), + o && (this.dt = n); + } + } + function d(e, r) { + var n = this.metrics, + i = e[0], + o = this; + if (n && i) { + var a = J(i); + a && (n.txSize = a); + } + (this.startTime = (0, c.t)()), + (this.body = i), + (this.listener = function (e) { + try { + 'abort' !== e.type || o.loadCaptureCalled || (o.params.aborted = !0), + ('load' !== e.type || + (o.called === o.totalCbs && + (o.onloadCalled || 'function' != typeof r.onload) && + 'function' == typeof o.end)) && + o.end(r); + } catch (e) { + try { + t.emit('internal-error', [e]); + } catch (e) {} + } + }); + for (var s = 0; s < we; s++) r.addEventListener(ye[s], this.listener, (0, I.jT)(!1)); + } + function l(e, t, r) { + (this.cbTime += e), + t ? (this.onloadCalled = !0) : (this.called += 1), + this.called !== this.totalCbs || + (!this.onloadCalled && 'function' == typeof r.onload) || + 'function' != typeof this.end || + this.end(r); + } + function f(e, t) { + var r = '' + Q(e) + !!t; + this.xhrGuids && !this.xhrGuids[r] && ((this.xhrGuids[r] = !0), (this.totalCbs += 1)); + } + function p(e, t) { + var r = '' + Q(e) + !!t; + this.xhrGuids && this.xhrGuids[r] && (delete this.xhrGuids[r], (this.totalCbs -= 1)); + } + function g() { + this.endTime = (0, c.t)(); + } + function m(e, r) { + r instanceof xe && 'load' === e[0] && t.emit('xhr-load-added', [e[1], e[2]], r); + } + function v(e, r) { + r instanceof xe && 'load' === e[0] && t.emit('xhr-load-removed', [e[1], e[2]], r); + } + function b(e, t, r) { + t instanceof xe && + ('onload' === r && (this.onload = !0), + ('load' === (e[0] && e[0].type) || this.onload) && (this.xhrCbStart = (0, c.t)())); + } + function w(e, r) { + this.xhrCbStart && t.emit('xhr-cb-time', [(0, c.t)() - this.xhrCbStart, this.onload, r], r); + } + function R(e) { + var t, + r = e[1] || {}; + if ( + ('string' == typeof e[0] + ? 0 === (t = e[0]).length && y.RI && (t = '' + y.gm.location.href) + : e[0] && e[0].url + ? (t = e[0].url) + : y.gm?.URL && e[0] && e[0] instanceof URL + ? (t = e[0].href) + : 'function' == typeof e[0].toString && (t = e[0].toString()), + 'string' == typeof t && 0 !== t.length) + ) { + t && ((this.parsedOrigin = (0, pe.D)(t)), (this.sameOrigin = this.parsedOrigin.sameOrigin)); + var n = i.generateTracePayload(this.parsedOrigin); + if (n && (n.newrelicHeader || n.traceContextParentHeader)) + if (e[0] && e[0].headers) s(e[0].headers, n) && (this.dt = n); + else { + var o = {}; + for (var a in r) o[a] = r[a]; + (o.headers = new Headers(r.headers || {})), + s(o.headers, n) && (this.dt = n), + e.length > 1 ? (e[1] = o) : e.push(o); + } + } + function s(e, t) { + var r = !1; + return ( + t.newrelicHeader && (e.set('newrelic', t.newrelicHeader), (r = !0)), + t.traceContextParentHeader && + (e.set('traceparent', t.traceContextParentHeader), + t.traceContextStateHeader && e.set('tracestate', t.traceContextStateHeader), + (r = !0)), + r + ); + } + } + function x(e, t) { + (this.params = {}), + (this.metrics = {}), + (this.startTime = (0, c.t)()), + (this.dt = t), + e.length >= 1 && (this.target = e[0]), + e.length >= 2 && (this.opts = e[1]); + var r = this.opts || {}, + n = this.target; + Ae(this, be(n)); + var i = ('' + ((n && n instanceof Re && n.method) || r.method || 'GET')).toUpperCase(); + (this.params.method = i), (this.body = r.body), (this.txSize = J(r.body) || 0); + } + function T(e, t) { + if (((this.endTime = (0, c.t)()), this.params || (this.params = {}), (0, ve.iW)(this.params))) return; + let i; + (this.params.status = t ? t.status : 0), + 'string' == typeof this.rxSize && this.rxSize.length > 0 && (i = +this.rxSize); + const o = { txSize: this.txSize, rxSize: i, duration: (0, c.t)() - this.startTime }; + r('xhr', [this.params, o, this.startTime, this.endTime, 'fetch'], this, n.K7.ajax); + } + function E(e) { + const t = this.params, + i = this.metrics; + if (!this.ended) { + this.ended = !0; + for (let t = 0; t < we; t++) e.removeEventListener(ye[t], this.listener, !1); + t.aborted || + (0, ve.iW)(t) || + ((i.duration = (0, c.t)() - this.startTime), + this.loadCaptureCalled || 4 !== e.readyState ? null == t.status && (t.status = 0) : A(this, e), + (i.cbTime = this.cbTime), + r('xhr', [t, i, this.startTime, this.endTime, 'xhr'], this, n.K7.ajax)); + } + } + function A(e, r) { + e.params.status = r.status; + var i = (function (e, t) { + var r = e.responseType; + return 'json' === r && null !== t + ? t + : 'arraybuffer' === r || 'blob' === r || 'json' === r + ? J(e.response) + : 'text' === r || '' === r || void 0 === r + ? J(e.responseText) + : void 0; + })(r, e.lastSize); + if ((i && (e.metrics.rxSize = i), e.sameOrigin && r.getAllResponseHeaders().indexOf(Te) >= 0)) { + var o = r.getResponseHeader(Te); + o && + ((0, s.p)(h.rs, ['Ajax/CrossApplicationTracing/Header/Seen'], void 0, n.K7.metrics, t), + (e.params.cat = o.split(', ').pop())); + } + e.loadCaptureCalled = !0; + } + t.on('new-xhr', o), + t.on('open-xhr-start', a), + t.on('open-xhr-end', u), + t.on('send-xhr-start', d), + t.on('xhr-cb-time', l), + t.on('xhr-load-added', f), + t.on('xhr-load-removed', p), + t.on('xhr-resolved', g), + t.on('addEventListener-end', m), + t.on('removeEventListener-end', v), + t.on('fn-end', w), + t.on('fetch-before-start', R), + t.on('fetch-start', x), + t.on('fn-start', b), + t.on('fetch-done', T); + })(e, this.ee, this.handler, this.dt), + this.importAggregator(e, () => i.e(478).then(i.bind(i, 3845))); + } + } + function Ae(e, t) { + var r = (0, pe.D)(t), + n = e.params || e; + (n.hostname = r.hostname), + (n.port = r.port), + (n.protocol = r.protocol), + (n.host = r.hostname + ':' + r.port), + (n.pathname = r.pathname), + (e.parsedOrigin = r), + (e.sameOrigin = r.sameOrigin); + } + const Oe = {}, + Se = ['pushState', 'replaceState']; + function Ne(e) { + const t = (function (e) { + return (e || te.ee).get('history'); + })(e); + return !y.RI || Oe[t.debugId]++ || ((Oe[t.debugId] = 1), (0, re.YM)(t).inPlace(window.history, Se, '-')), t; + } + var _e = i(3738); + function Ie(e) { + p( + u.BL, + function (t = Date.now()) { + const r = t - y.WN; + r < 0 && (0, l.R)(62, t), + (0, s.p)(h.XG, [u.BL, { time: r }], void 0, n.K7.metrics, e.ee), + e.addToTrace({ name: u.BL, start: t, origin: 'nr' }), + (0, s.p)(u.Pl + u.hG, [r, u.BL], void 0, n.K7.genericEvents, e.ee); + }, + e + ); + } + const { He: Pe, bD: je, d3: Ce, Kp: ke, TZ: Le, Lc: He, uP: Me, Rz: De } = _e; + class Ke extends E { + static featureName = Le; + constructor(e) { + var t; + super(e, Le), + (t = e), + p( + u.U2, + function (e) { + if (!(e && 'object' == typeof e && e.name && e.start)) return; + const r = { n: e.name, s: e.start - y.WN, e: (e.end || e.start) - y.WN, o: e.origin || '', t: 'api' }; + r.s < 0 || r.e < 0 || r.e < r.s + ? (0, l.R)(61, { start: r.s, end: r.e }) + : (0, s.p)('bstApi', [r], void 0, n.K7.sessionTrace, t.ee); + }, + t + ), + Ie(e); + if (!(0, R.V)(e.init)) return void this.deregisterDrain(); + const r = this.ee; + let o; + Ne(r), + (this.eventsEE = (0, ee.u)(r)), + this.eventsEE.on(Me, function (e, t) { + this.bstStart = (0, c.t)(); + }), + this.eventsEE.on(He, function (e, t) { + (0, s.p)('bst', [e[0], t, this.bstStart, (0, c.t)()], void 0, n.K7.sessionTrace, r); + }), + r.on(De + Ce, function (e) { + (this.time = (0, c.t)()), (this.startPath = location.pathname + location.hash); + }), + r.on(De + ke, function (e) { + (0, s.p)( + 'bstHist', + [location.pathname + location.hash, this.startPath, this.time], + void 0, + n.K7.sessionTrace, + r + ); + }); + try { + (o = new PerformanceObserver((e) => { + const t = e.getEntries(); + (0, s.p)(Pe, [t], void 0, n.K7.sessionTrace, r); + })), + o.observe({ type: je, buffered: !0 }); + } catch (e) {} + this.importAggregator(e, () => i.e(478).then(i.bind(i, 6974)), { resourceObserver: o }); + } + } + var Ue = i(6344); + class Fe extends E { + static featureName = Ue.TZ; + #n; + recorder; + constructor(e) { + var t; + let r; + super(e, Ue.TZ), + (t = e), + p( + u.CH, + function () { + (0, s.p)(u.CH, [], void 0, n.K7.sessionReplay, t.ee); + }, + t + ), + (function (e) { + p( + u.Tb, + function () { + (0, s.p)(u.Tb, [], void 0, n.K7.sessionReplay, e.ee); + }, + e + ); + })(e); + try { + r = JSON.parse(localStorage.getItem(''.concat(O.H3, '_').concat(O.uh))); + } catch (e) {} + (0, w.SR)(e.init) && this.ee.on(Ue.G4.RECORD, () => this.#i()), + this.#o(r) && + this.importRecorder().then((e) => { + e.startRecording(Ue.Qb.PRELOAD, r?.sessionReplayMode); + }), + this.importAggregator(this.agentRef, () => i.e(478).then(i.bind(i, 6167)), this), + this.ee.on('err', (e) => { + this.blocked || + (this.agentRef.runtime.isRecording && + ((this.errorNoticed = !0), + (0, s.p)(Ue.G4.ERROR_DURING_REPLAY, [e], void 0, this.featureName, this.ee))); + }); + } + #o(e) { + return ( + (e && (e.sessionReplayMode === O.g.FULL || e.sessionReplayMode === O.g.ERROR)) || + (0, w.Aw)(this.agentRef.init) + ); + } + importRecorder() { + return this.recorder + ? Promise.resolve(this.recorder) + : ((this.#n ??= Promise.all([i.e(478), i.e(249)]) + .then(i.bind(i, 4866)) + .then(({ Recorder: e }) => ((this.recorder = new e(this)), this.recorder)) + .catch((e) => { + throw (this.ee.emit('internal-error', [e]), (this.blocked = !0), e); + })), + this.#n); + } + #i() { + this.blocked || + (this.featAggregate + ? this.featAggregate.mode !== O.g.FULL && this.featAggregate.initializeRecording(O.g.FULL, !0, Ue.Qb.API) + : this.importRecorder().then(() => { + this.recorder.startRecording(Ue.Qb.API, O.g.FULL); + })); + } + } + var We = i(3962); + function Be(e) { + const t = e.ee.get('tracer'); + function r() {} + p( + u.dT, + function (e) { + return new r().get('object' == typeof e ? e : {}); + }, + e + ); + const i = (r.prototype = { + createTracer: function (r, i) { + var o = {}, + a = this, + d = 'function' == typeof i; + return ( + (0, s.p)(h.xV, ['API/createTracer/called'], void 0, n.K7.metrics, e.ee), + e.runSoftNavOverSpa || (0, s.p)(u.hw + 'tracer', [(0, c.t)(), r, o], a, n.K7.spa, e.ee), + function () { + if ((t.emit((d ? '' : 'no-') + 'fn-start', [(0, c.t)(), a, d], o), d)) + try { + return i.apply(this, arguments); + } catch (e) { + const r = 'string' == typeof e ? new Error(e) : e; + throw (t.emit('fn-err', [arguments, this, r], o), r); + } finally { + t.emit('fn-end', [(0, c.t)()], o); + } + } + ); + }, + }); + ['actionText', 'setName', 'setAttribute', 'save', 'ignore', 'onEnd', 'getContext', 'end', 'get'].forEach( + (t) => { + p.apply(this, [ + t, + function () { + return ( + (0, s.p)( + u.hw + t, + [(0, c.t)(), ...arguments], + this, + e.runSoftNavOverSpa ? n.K7.softNav : n.K7.spa, + e.ee + ), + this + ); + }, + e, + i, + ]); + } + ), + p( + u.PA, + function () { + e.runSoftNavOverSpa + ? (0, s.p)(u.hw + 'routeName', [performance.now(), ...arguments], void 0, n.K7.softNav, e.ee) + : (0, s.p)(u.Pl + 'routeName', [(0, c.t)(), ...arguments], this, n.K7.spa, e.ee); + }, + e + ); + } + class Ge extends E { + static featureName = We.TZ; + constructor(e) { + if ((super(e, We.TZ), Be(e), !y.RI || !(0, N.dV)().o.MO)) return; + const t = Ne(this.ee); + try { + this.removeOnAbort = new AbortController(); + } catch (e) {} + We.tC.forEach((e) => { + (0, I.sp)( + e, + (e) => { + a(e); + }, + !0, + this.removeOnAbort?.signal + ); + }); + const r = () => (0, s.p)('newURL', [(0, c.t)(), '' + window.location], void 0, this.featureName, this.ee); + t.on('pushState-end', r), + t.on('replaceState-end', r), + (0, I.sp)( + We.OV, + (e) => { + a(e), (0, s.p)('newURL', [e.timeStamp, '' + window.location], void 0, this.featureName, this.ee); + }, + !0, + this.removeOnAbort?.signal + ); + let n = !1; + const o = new ((0, N.dV)().o.MO)((e, t) => { + n || + ((n = !0), + requestAnimationFrame(() => { + (0, s.p)('newDom', [(0, c.t)()], void 0, this.featureName, this.ee), (n = !1); + })); + }), + a = (0, x.s)( + (e) => { + (0, s.p)('newUIEvent', [e], void 0, this.featureName, this.ee), + o.observe(document.body, { attributes: !0, childList: !0, subtree: !0, characterData: !0 }); + }, + 100, + { leading: !0 } + ); + (this.abortHandler = function () { + this.removeOnAbort?.abort(), o.disconnect(), (this.abortHandler = void 0); + }), + this.importAggregator(e, () => i.e(478).then(i.bind(i, 4393)), { domObserver: o }); + } + } + var Ve = i(7378); + const ze = {}, + Ze = ['appendChild', 'insertBefore', 'replaceChild']; + function qe(e) { + const t = (function (e) { + return (e || te.ee).get('jsonp'); + })(e); + if (!y.RI || ze[t.debugId]) return t; + ze[t.debugId] = !0; + var r = (0, re.YM)(t), + n = /[?&](?:callback|cb)=([^&#]+)/, + i = /(.*)\.([^.]+)/, + o = /^(\w+)(\.|$)(.*)$/; + function a(e, t) { + if (!e) return t; + const r = e.match(o), + n = r[1]; + return a(r[3], t[n]); + } + return ( + r.inPlace(Node.prototype, Ze, 'dom-'), + t.on('dom-start', function (e) { + !(function (e) { + if (!e || 'string' != typeof e.nodeName || 'script' !== e.nodeName.toLowerCase()) return; + if ('function' != typeof e.addEventListener) return; + var o = ((s = e.src), (c = s.match(n)), c ? c[1] : null); + var s, c; + if (!o) return; + var u = (function (e) { + var t = e.match(i); + if (t && t.length >= 3) return { key: t[2], parent: a(t[1], window) }; + return { key: e, parent: window }; + })(o); + if ('function' != typeof u.parent[u.key]) return; + var d = {}; + function l() { + t.emit('jsonp-end', [], d), + e.removeEventListener('load', l, (0, I.jT)(!1)), + e.removeEventListener('error', f, (0, I.jT)(!1)); + } + function f() { + t.emit('jsonp-error', [], d), + t.emit('jsonp-end', [], d), + e.removeEventListener('load', l, (0, I.jT)(!1)), + e.removeEventListener('error', f, (0, I.jT)(!1)); + } + r.inPlace(u.parent, [u.key], 'cb-', d), + e.addEventListener('load', l, (0, I.jT)(!1)), + e.addEventListener('error', f, (0, I.jT)(!1)), + t.emit('new-jsonp', [e.src], d); + })(e[0]); + }), + t + ); + } + const Xe = {}; + function Ye(e) { + const t = (function (e) { + return (e || te.ee).get('promise'); + })(e); + if (Xe[t.debugId]) return t; + Xe[t.debugId] = !0; + var r = t.context, + n = (0, re.YM)(t), + i = y.gm.Promise; + return ( + i && + (function () { + function e(r) { + var o = t.context(), + a = n(r, 'executor-', o, null, !1); + const s = Reflect.construct(i, [a], e); + return ( + (t.context(s).getCtx = function () { + return o; + }), + s + ); + } + (y.gm.Promise = e), + Object.defineProperty(e, 'name', { value: 'Promise' }), + (e.toString = function () { + return i.toString(); + }), + Object.setPrototypeOf(e, i), + ['all', 'race'].forEach(function (r) { + const n = i[r]; + e[r] = function (e) { + let i = !1; + [...(e || [])].forEach((e) => { + this.resolve(e).then(a('all' === r), a(!1)); + }); + const o = n.apply(this, arguments); + return o; + function a(e) { + return function () { + t.emit('propagate', [null, !i], o, !1, !1), (i = i || !e); + }; + } + }; + }), + ['resolve', 'reject'].forEach(function (r) { + const n = i[r]; + e[r] = function (e) { + const r = n.apply(this, arguments); + return e !== r && t.emit('propagate', [e, !0], r, !1, !1), r; + }; + }), + (e.prototype = i.prototype); + const o = i.prototype.then; + (i.prototype.then = function (...e) { + var i = this, + a = r(i); + (a.promise = i), (e[0] = n(e[0], 'cb-', a, null, !1)), (e[1] = n(e[1], 'cb-', a, null, !1)); + const s = o.apply(this, e); + return (a.nextPromise = s), t.emit('propagate', [i, !0], s, !1, !1), s; + }), + (i.prototype.then[re.Jt] = o), + t.on('executor-start', function (e) { + (e[0] = n(e[0], 'resolve-', this, null, !1)), (e[1] = n(e[1], 'resolve-', this, null, !1)); + }), + t.on('executor-err', function (e, t, r) { + e[1](r); + }), + t.on('cb-end', function (e, r, n) { + t.emit('propagate', [n, !0], this.nextPromise, !1, !1); + }), + t.on('propagate', function (e, r, n) { + (this.getCtx && !r) || + (this.getCtx = function () { + if (e instanceof Promise) var r = t.context(e); + return r && r.getCtx ? r.getCtx() : this; + }); + }); + })(), + t + ); + } + const Qe = {}, + $e = 'setTimeout', + Je = 'setInterval', + et = 'clearTimeout', + tt = '-start', + rt = [$e, 'setImmediate', Je, et, 'clearImmediate']; + function nt(e) { + const t = (function (e) { + return (e || te.ee).get('timer'); + })(e); + if (Qe[t.debugId]++) return t; + Qe[t.debugId] = 1; + var r = (0, re.YM)(t); + return ( + r.inPlace(y.gm, rt.slice(0, 2), $e + '-'), + r.inPlace(y.gm, rt.slice(2, 3), Je + '-'), + r.inPlace(y.gm, rt.slice(3), et + '-'), + t.on(Je + tt, function (e, t, n) { + e[0] = r(e[0], 'fn-', null, n); + }), + t.on($e + tt, function (e, t, n) { + (this.method = n), (this.timerDuration = isNaN(e[1]) ? 0 : +e[1]), (e[0] = r(e[0], 'fn-', this, n)); + }), + t + ); + } + const it = {}; + function ot(e) { + const t = (function (e) { + return (e || te.ee).get('mutation'); + })(e); + if (!y.RI || it[t.debugId]) return t; + it[t.debugId] = !0; + var r = (0, re.YM)(t), + n = y.gm.MutationObserver; + return ( + n && + ((window.MutationObserver = function (e) { + return this instanceof n ? new n(r(e, 'fn-')) : n.apply(this, arguments); + }), + (MutationObserver.prototype = n.prototype)), + t + ); + } + const { TZ: at, d3: st, Kp: ct, $p: ut, wW: dt, e5: lt, tH: ft, uP: ht, rw: pt, Lc: gt } = Ve; + class mt extends E { + static featureName = at; + constructor(e) { + if ((super(e, at), Be(e), !y.RI)) return; + try { + this.removeOnAbort = new AbortController(); + } catch (e) {} + let t, + r = 0; + const n = this.ee.get('tracer'), + o = qe(this.ee), + a = Ye(this.ee), + u = nt(this.ee), + d = oe(this.ee), + l = this.ee.get('events'), + f = he(this.ee), + h = Ne(this.ee), + p = ot(this.ee); + function g(e, t) { + h.emit('newURL', ['' + window.location, t]); + } + function m() { + r++, (t = window.location.hash), (this[ht] = (0, c.t)()); + } + function v() { + r--, window.location.hash !== t && g(0, !0); + var e = (0, c.t)(); + (this[lt] = ~~this[lt] + e - this[ht]), (this[gt] = e); + } + function b(e, t) { + e.on(t, function () { + this[t] = (0, c.t)(); + }); + } + this.ee.on(ht, m), + a.on(pt, m), + o.on(pt, m), + this.ee.on(gt, v), + a.on(dt, v), + o.on(dt, v), + this.ee.on('fn-err', (...t) => { + t[2]?.__newrelic?.[e.agentIdentifier] || + (0, s.p)('function-err', [...t], void 0, this.featureName, this.ee); + }), + this.ee.buffer([ht, gt, 'xhr-resolved'], this.featureName), + l.buffer([ht], this.featureName), + u.buffer(['setTimeout' + ct, 'clearTimeout' + st, ht], this.featureName), + d.buffer([ht, 'new-xhr', 'send-xhr' + st], this.featureName), + f.buffer([ft + st, ft + '-done', ft + ut + st, ft + ut + ct], this.featureName), + h.buffer(['newURL'], this.featureName), + p.buffer([ht], this.featureName), + a.buffer(['propagate', pt, dt, 'executor-err', 'resolve' + st], this.featureName), + n.buffer([ht, 'no-' + ht], this.featureName), + o.buffer(['new-jsonp', 'cb-start', 'jsonp-error', 'jsonp-end'], this.featureName), + b(f, ft + st), + b(f, ft + '-done'), + b(o, 'new-jsonp'), + b(o, 'jsonp-end'), + b(o, 'cb-start'), + h.on('pushState-end', g), + h.on('replaceState-end', g), + window.addEventListener('hashchange', g, (0, I.jT)(!0, this.removeOnAbort?.signal)), + window.addEventListener('load', g, (0, I.jT)(!0, this.removeOnAbort?.signal)), + window.addEventListener( + 'popstate', + function () { + g(0, r > 1); + }, + (0, I.jT)(!0, this.removeOnAbort?.signal) + ), + (this.abortHandler = this.#r), + this.importAggregator(e, () => i.e(478).then(i.bind(i, 5592))); + } + #r() { + this.removeOnAbort?.abort(), (this.abortHandler = void 0); + } + } + var vt = i(3333); + class bt extends E { + static featureName = vt.TZ; + constructor(e) { + super(e, vt.TZ); + const t = [ + e.init.page_action.enabled, + e.init.performance.capture_marks, + e.init.performance.capture_measures, + e.init.user_actions.enabled, + e.init.performance.resources.enabled, + ]; + var r; + if ( + ((r = e), + p(u.hG, (e, t) => z(e, t, r), r), + (function (e) { + p( + u.fF, + function () { + (0, s.p)(u.Pl + u.fF, [(0, c.t)(), ...arguments], void 0, n.K7.genericEvents, e.ee); + }, + e + ); + })(e), + Ie(e), + Z(e), + (function (e) { + p( + u.V1, + function (t, r) { + const i = (0, c.t)(), + { start: o, end: a, customAttributes: d } = r || {}, + f = { customAttributes: d || {} }; + if ('object' != typeof f.customAttributes || 'string' != typeof t || 0 === t.length) + return void (0, l.R)(57); + const h = (e, t) => + null == e ? t : 'number' == typeof e ? e : e instanceof PerformanceMark ? e.startTime : Number.NaN; + if (((f.start = h(o, 0)), (f.end = h(a, i)), Number.isNaN(f.start) || Number.isNaN(f.end))) + (0, l.R)(57); + else { + if (((f.duration = f.end - f.start), !(f.duration < 0))) + return (0, s.p)(u.Pl + u.V1, [f, t], void 0, n.K7.genericEvents, e.ee), f; + (0, l.R)(58); + } + }, + e + ); + })(e), + y.RI) + ) { + if ( + (e.init.user_actions.enabled && + (vt.Zp.forEach((e) => (0, I.sp)(e, (e) => (0, s.p)('ua', [e], void 0, this.featureName, this.ee), !0)), + vt.qN.forEach((e) => { + const t = (0, x.s)( + (e) => { + (0, s.p)('ua', [e], void 0, this.featureName, this.ee); + }, + 500, + { leading: !0 } + ); + (0, I.sp)(e, t); + })), + e.init.performance.resources.enabled && + y.gm.PerformanceObserver?.supportedEntryTypes.includes('resource')) + ) { + new PerformanceObserver((e) => { + e.getEntries().forEach((e) => { + (0, s.p)('browserPerformance.resource', [e], void 0, this.featureName, this.ee); + }); + }).observe({ type: 'resource', buffered: !0 }); + } + const a = Ne(this.ee); + function d() { + a.emit('navChange'); + } + a.on('pushState-end', d), + a.on('replaceState-end', d), + window.addEventListener('hashchange', d, (0, I.jT)(!0, this.removeOnAbort?.signal)), + window.addEventListener('popstate', d, (0, I.jT)(!0, this.removeOnAbort?.signal)); + } + try { + this.removeOnAbort = new AbortController(); + } catch (f) {} + function o(t) { + const r = (0, pe.D)(t); + return e.beacons.includes(r.hostname + ':' + r.port); + } + (this.abortHandler = () => { + this.removeOnAbort?.abort(), (this.abortHandler = void 0); + }), + y.gm.addEventListener( + 'error', + () => { + (0, s.p)('uaErr', [], void 0, n.K7.genericEvents, this.ee); + }, + (0, I.jT)(!1, this.removeOnAbort?.signal) + ), + he(this.ee), + oe(this.ee), + this.ee.on('open-xhr-start', (e, t) => { + o(e[1]) || + t.addEventListener('readystatechange', () => { + 2 === t.readyState && (0, s.p)('uaXhr', [], void 0, n.K7.genericEvents, this.ee); + }); + }), + this.ee.on('fetch-start', (e) => { + e.length >= 1 && !o(be(e[0])) && (0, s.p)('uaXhr', [], void 0, n.K7.genericEvents, this.ee); + }), + t.some((e) => e) ? this.importAggregator(e, () => i.e(478).then(i.bind(i, 8019))) : this.deregisterDrain(); + } + } + var yt = i(2646); + const wt = new Map(); + function Rt(e, t, r, n) { + if ('object' != typeof t || !t || 'string' != typeof r || !r || 'function' != typeof t[r]) return (0, l.R)(29); + const i = (function (e) { + return (e || te.ee).get('logger'); + })(e), + o = (0, re.YM)(i), + a = new yt.y(te.P); + (a.level = n.level), (a.customAttributes = n.customAttributes); + const s = t[r]?.[re.Jt] || t[r]; + return wt.set(s, a), o.inPlace(t, [r], 'wrap-logger-', () => wt.get(s)), i; + } + var xt = i(1910); + class Tt extends E { + static featureName = B.TZ; + constructor(e) { + var t; + super(e, B.TZ), + (t = e), + p(u.$9, (e, r) => V(e, r, t), t), + (function (e) { + p( + u.Wb, + (t, r, { customAttributes: n = {}, level: i = B.p_.INFO } = {}) => { + Rt(e.ee, t, r, { customAttributes: n, level: i }); + }, + e + ); + })(e), + Z(e); + const r = this.ee; + ['log', 'error', 'warn', 'info', 'debug', 'trace'].forEach((e) => { + (0, xt.i)(y.gm.console[e]), Rt(r, y.gm.console, e, { level: 'log' === e ? 'info' : e }); + }), + this.ee.on('wrap-logger-end', function ([e]) { + const { level: t, customAttributes: n } = this; + (0, G.R)(r, e, n, t); + }), + this.importAggregator(e, () => i.e(478).then(i.bind(i, 5288))); + } + } + new (class extends r { + constructor(e) { + var t; + (super(), y.gm) + ? ((this.features = {}), + (0, N.bQ)(this.agentIdentifier, this), + (this.desiredFeatures = new Set(e.features || [])), + this.desiredFeatures.add(S), + (this.runSoftNavOverSpa = [...this.desiredFeatures].some((e) => e.featureName === n.K7.softNav)), + (0, a.j)(this, e, e.loaderType || 'agent'), + (t = this), + p( + u.cD, + function (e, r, n = !1) { + if ('string' == typeof e) { + if (['string', 'number', 'boolean'].includes(typeof r) || null === r) return g(t, e, r, u.cD, n); + (0, l.R)(40, typeof r); + } else (0, l.R)(39, typeof e); + }, + t + ), + (function (e) { + p( + u.Dl, + function (t) { + if ('string' == typeof t || null === t) return g(e, 'enduser.id', t, u.Dl, !0); + (0, l.R)(41, typeof t); + }, + e + ); + })(this), + (function (e) { + p( + u.nb, + function (t) { + if ('string' == typeof t || null === t) return g(e, 'application.version', t, u.nb, !1); + (0, l.R)(42, typeof t); + }, + e + ); + })(this), + (function (e) { + p( + u.d3, + function () { + e.ee.emit('manual-start-all'); + }, + e + ); + })(this), + this.run()) + : (0, l.R)(21); + } + get config() { + return { info: this.info, init: this.init, loader_config: this.loader_config, runtime: this.runtime }; + } + get api() { + return this; + } + run() { + try { + const e = (function (e) { + const t = {}; + return ( + o.forEach((r) => { + t[r] = !!e[r]?.enabled; + }), + t + ); + })(this.init), + t = [...this.desiredFeatures]; + t.sort((e, t) => n.P3[e.featureName] - n.P3[t.featureName]), + t.forEach((t) => { + if (!e[t.featureName] && t.featureName !== n.K7.pageViewEvent) return; + if (this.runSoftNavOverSpa && t.featureName === n.K7.spa) return; + if (!this.runSoftNavOverSpa && t.featureName === n.K7.softNav) return; + const r = (function (e) { + switch (e) { + case n.K7.ajax: + return [n.K7.jserrors]; + case n.K7.sessionTrace: + return [n.K7.ajax, n.K7.pageViewEvent]; + case n.K7.sessionReplay: + return [n.K7.sessionTrace]; + case n.K7.pageViewTiming: + return [n.K7.pageViewEvent]; + default: + return []; + } + })(t.featureName).filter((e) => !(e in this.features)); + r.length > 0 && (0, l.R)(36, { targetFeature: t.featureName, missingDependencies: r }), + (this.features[t.featureName] = new t(this)); + }); + } catch (e) { + (0, l.R)(22, e); + for (const e in this.features) this.features[e].abortHandler?.(); + const t = (0, N.Zm)(); + delete t.initializedAgents[this.agentIdentifier]?.features, delete this.sharedAggregator; + return t.ee.get(this.agentIdentifier).abort(), !1; + } + } + })({ features: [Ee, S, j, Ke, Fe, C, q, bt, Tt, Ge, mt], loaderType: 'spa' }); + })(); +})(); diff --git a/src/environments/environment.development.ts b/src/environments/environment.development.ts index 9d7ee2eed..c4230b680 100644 --- a/src/environments/environment.development.ts +++ b/src/environments/environment.development.ts @@ -68,7 +68,7 @@ export const environment = { newRelicInfoSa: 1, newRelicLoaderConfigAccountID: '', newRelicLoaderConfigTrustKey: '', - newRelicLoaderConfigAgengID: '', + newRelicLoaderConfigAgentID: '', newRelicLoaderConfigLicenseKey: '', newRelicLoaderConfigApplicationID: '', }; diff --git a/src/environments/environment.docker.ts b/src/environments/environment.docker.ts index 9fd75b469..92a84f2ee 100644 --- a/src/environments/environment.docker.ts +++ b/src/environments/environment.docker.ts @@ -25,7 +25,7 @@ export const environment = { newRelicInfoSa: 1, newRelicLoaderConfigAccountID: '', newRelicLoaderConfigTrustKey: '', - newRelicLoaderConfigAgengID: '', + newRelicLoaderConfigAgentID: '', newRelicLoaderConfigLicenseKey: '', newRelicLoaderConfigApplicationID: '', }; diff --git a/src/environments/environment.staging.ts b/src/environments/environment.staging.ts index ff0f83afa..a7725102f 100644 --- a/src/environments/environment.staging.ts +++ b/src/environments/environment.staging.ts @@ -68,7 +68,7 @@ export const environment = { newRelicInfoSa: 1, newRelicLoaderConfigAccountID: '', newRelicLoaderConfigTrustKey: '', - newRelicLoaderConfigAgengID: '', + newRelicLoaderConfigAgentID: '', newRelicLoaderConfigLicenseKey: '', newRelicLoaderConfigApplicationID: '', }; diff --git a/src/environments/environment.test-osf.ts b/src/environments/environment.test-osf.ts index 7f50d1a6b..87f7a0c6f 100644 --- a/src/environments/environment.test-osf.ts +++ b/src/environments/environment.test-osf.ts @@ -28,7 +28,7 @@ export const environment = { newRelicInfoSa: 1, newRelicLoaderConfigAccountID: '', newRelicLoaderConfigTrustKey: '', - newRelicLoaderConfigAgengID: '', + newRelicLoaderConfigAgentID: '', newRelicLoaderConfigLicenseKey: '', newRelicLoaderConfigApplicationID: '', }; diff --git a/src/environments/environment.test.ts b/src/environments/environment.test.ts index 3d042aff7..08568d965 100644 --- a/src/environments/environment.test.ts +++ b/src/environments/environment.test.ts @@ -28,7 +28,7 @@ export const environment = { newRelicInfoSa: 1, newRelicLoaderConfigAccountID: '', newRelicLoaderConfigTrustKey: '', - newRelicLoaderConfigAgengID: '', + newRelicLoaderConfigAgentID: '', newRelicLoaderConfigLicenseKey: '', newRelicLoaderConfigApplicationID: '', }; diff --git a/src/environments/environment.ts b/src/environments/environment.ts index ff0f83afa..a7725102f 100644 --- a/src/environments/environment.ts +++ b/src/environments/environment.ts @@ -68,7 +68,7 @@ export const environment = { newRelicInfoSa: 1, newRelicLoaderConfigAccountID: '', newRelicLoaderConfigTrustKey: '', - newRelicLoaderConfigAgengID: '', + newRelicLoaderConfigAgentID: '', newRelicLoaderConfigLicenseKey: '', newRelicLoaderConfigApplicationID: '', }; diff --git a/src/index.html b/src/index.html index bf35c18ca..7c92fb628 100644 --- a/src/index.html +++ b/src/index.html @@ -8,6 +8,7 @@ + diff --git a/src/main.ts b/src/main.ts index 62b5a3c94..e005d74a2 100644 --- a/src/main.ts +++ b/src/main.ts @@ -3,9 +3,6 @@ import { bootstrapApplication } from '@angular/platform-browser'; import { AppComponent } from '@osf/app.component'; import { appConfig } from '@osf/app.config'; -// Import CEDAR Embeddable Editor web component -import 'cedar-embeddable-editor'; - bootstrapApplication(AppComponent, { providers: [...appConfig.providers], }).catch((err) => diff --git a/src/styles/_fonts.scss b/src/styles/_fonts.scss index 1dda8264e..45559d13f 100644 --- a/src/styles/_fonts.scss +++ b/src/styles/_fonts.scss @@ -45,3 +45,7 @@ font-style: normal; font-display: swap; } + +// Override default value in font awesome library + +$fa-font-display: swap; diff --git a/src/styles/overrides/accordion.scss b/src/styles/overrides/accordion.scss index 221e87564..c2121cb4d 100644 --- a/src/styles/overrides/accordion.scss +++ b/src/styles/overrides/accordion.scss @@ -46,7 +46,7 @@ row-gap: 1.5rem; p { - font-weight: 300; + font-weight: 400; } } } diff --git a/src/styles/overrides/table.scss b/src/styles/overrides/table.scss index a27d956b4..416cee613 100644 --- a/src/styles/overrides/table.scss +++ b/src/styles/overrides/table.scss @@ -85,10 +85,13 @@ p-table { td, th { - background-color: transparent; border-bottom: 1px solid var(--grey-2); } + td { + background-color: transparent; + } + tr { &:hover { background: transparent; diff --git a/src/styles/styles.scss b/src/styles/styles.scss index f192f1d5d..a51b536b8 100644 --- a/src/styles/styles.scss +++ b/src/styles/styles.scss @@ -7,6 +7,11 @@ @use "base"; @use "icons"; +@use "@fortawesome/fontawesome-free/scss/fontawesome.scss"; +@use "@fortawesome/fontawesome-free/scss/solid.scss"; +@use "@fortawesome/fontawesome-free/scss/brands.scss"; +@use "@fortawesome/fontawesome-free/scss/regular.scss"; + @use "./components/md-editor"; @use "./components/preprints"; @use "./components/collections"; diff --git a/src/testing/mocks/environment.token.mock.ts b/src/testing/mocks/environment.token.mock.ts index 1c8c64216..02105aed2 100644 --- a/src/testing/mocks/environment.token.mock.ts +++ b/src/testing/mocks/environment.token.mock.ts @@ -32,5 +32,20 @@ export const EnvironmentTokenMock = { webUrl: 'http://localhost:4200', supportEmail: 'support@test.com', defaultProvider: 'osf', + newRelicEnabled: false, + newRelicInitDistributedTracingEnabled: false, + newRelicInitPerformanceCaptureMeasures: false, + newRelicInitPrivacyCookiesEnabled: false, + newRelicInitAjaxDenyList: [], + newRelicInfoBeacon: '', + newRelicInfoErrorBeacon: '', + newRelicInfoLicenseKey: '', + newRelicInfoApplicationID: '', + newRelicInfoSa: 1, + newRelicLoaderConfigAccountID: '', + newRelicLoaderConfigTrustKey: '', + newRelicLoaderConfigAgentID: '', + newRelicLoaderConfigLicenseKey: '', + newRelicLoaderConfigApplicationID: '', }, }; diff --git a/src/testing/mocks/new-relic.mock.ts b/src/testing/mocks/new-relic.mock.ts new file mode 100644 index 000000000..afec7c6c7 --- /dev/null +++ b/src/testing/mocks/new-relic.mock.ts @@ -0,0 +1,17 @@ +export const NEW_RELIC_CONFIG_MOCK = { + newRelicEnabled: true, + newRelicInitDistributedTracingEnabled: true, + newRelicInitPerformanceCaptureMeasures: true, + newRelicInitPrivacyCookiesEnabled: true, + newRelicInitAjaxDenyList: ['test-url'], + newRelicInfoBeacon: 'test-beacon', + newRelicInfoErrorBeacon: 'test-error-beacon', + newRelicInfoLicenseKey: 'test-license-key', + newRelicInfoApplicationID: 'test-app-id', + newRelicInfoSa: 1, + newRelicLoaderConfigAccountID: 'test-account-id', + newRelicLoaderConfigTrustKey: 'test-trust-key', + newRelicLoaderConfigAgentID: 'test-agent-id', + newRelicLoaderConfigLicenseKey: 'test-loader-license-key', + newRelicLoaderConfigApplicationID: 'test-loader-app-id', +};