diff --git a/src/app/app.routes.ts b/src/app/app.routes.ts index f20059438..e9c60ef9c 100644 --- a/src/app/app.routes.ts +++ b/src/app/app.routes.ts @@ -5,6 +5,8 @@ import { Routes } from '@angular/router'; 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 { MyProfileResourceFiltersOptionsState } from './features/my-profile/components/filters/store'; import { MyProfileResourceFiltersState } from './features/my-profile/components/my-profile-resource-filters/store'; import { MyProfileState } from './features/my-profile/store'; @@ -99,12 +101,6 @@ export const routes: Routes = [ ), providers: [provideStates([PreprintState])], }, - { - path: 'project/:id', - loadChildren: () => import('./features/project/project.routes').then((mod) => mod.projectRoutes), - providers: [provideStates([ProjectsState, BookmarksState])], - canActivate: [authGuard], - }, { path: 'preprints', loadChildren: () => import('./features/preprints/preprints.routes').then((mod) => mod.preprintsRoutes), @@ -120,12 +116,6 @@ export const routes: Routes = [ path: 'registries', loadChildren: () => import('./features/registries/registries.routes').then((mod) => mod.registriesRoutes), }, - { - path: 'registries/:id', - loadChildren: () => import('./features/registry/registry.routes').then((mod) => mod.registryRoutes), - providers: [provideStates([BookmarksState])], - canActivate: [authGuard], - }, { path: 'my-profile', loadComponent: () => import('./features/my-profile/my-profile.component').then((mod) => mod.MyProfileComponent), @@ -178,6 +168,18 @@ export const routes: Routes = [ loadComponent: () => import('@osf/features/files/pages/file-detail/file-detail.component').then((c) => c.FileDetailComponent), }, + { + path: ':id', + canMatch: [isProjectGuard], + loadChildren: () => import('./features/project/project.routes').then((m) => m.projectRoutes), + providers: [provideStates([ProjectsState, BookmarksState])], + }, + { + path: ':id', + canMatch: [isRegistryGuard], + loadChildren: () => import('./features/registry/registry.routes').then((m) => m.registryRoutes), + providers: [provideStates([BookmarksState])], + }, { path: '**', loadComponent: () => 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 a4415dad9..87367469b 100644 --- a/src/app/core/components/nav-menu/nav-menu.component.ts +++ b/src/app/core/components/nav-menu/nav-menu.component.ts @@ -17,7 +17,9 @@ import { RouteContext } from '@osf/core/models'; import { AuthService } from '@osf/core/services'; import { UserSelectors } from '@osf/core/store/user'; import { IconComponent } from '@osf/shared/components'; +import { CurrentResourceType } from '@osf/shared/enums'; import { WrapFnPipe } from '@osf/shared/pipes'; +import { CurrentResourceSelectors } from '@osf/shared/stores'; @Component({ selector: 'osf-nav-menu', @@ -33,6 +35,7 @@ export class NavMenuComponent { private readonly authService = inject(AuthService); private readonly isAuthenticated = select(UserSelectors.isAuthenticated); + private readonly currentResource = select(CurrentResourceSelectors.getCurrentResource); protected readonly mainMenuItems = computed(() => { const isAuthenticated = this.isAuthenticated(); @@ -41,8 +44,12 @@ export class NavMenuComponent { const routeContext: RouteContext = { resourceId: this.currentResourceId(), providerId: this.currentProviderId(), - isProject: this.isProjectRoute() && !this.isRegistryRoute() && !this.isPreprintRoute(), - isRegistry: this.isRegistryRoute(), + isProject: + this.currentResource()?.type === CurrentResourceType.Projects && + this.currentResourceId() === this.currentResource()?.id, + isRegistry: + this.currentResource()?.type === CurrentResourceType.Registrations && + this.currentResourceId() === this.currentResource()?.id, isPreprint: this.isPreprintRoute(), preprintReviewsPageVisible: this.canUserViewReviews(), isCollections: this.isCollectionsRoute() || false, @@ -66,9 +73,7 @@ export class NavMenuComponent { protected readonly currentResourceId = computed(() => this.currentRoute().resourceId); protected readonly currentProviderId = computed(() => this.currentRoute().providerId); - protected readonly isProjectRoute = computed(() => !!this.currentResourceId()); protected readonly isCollectionsRoute = computed(() => this.currentRoute().isCollectionsWithId); - protected readonly isRegistryRoute = computed(() => this.currentRoute().isRegistryRoute); protected readonly isPreprintRoute = computed(() => this.currentRoute().isPreprintRoute); protected readonly canUserViewReviews = select(UserSelectors.getCanViewReviews); @@ -78,14 +83,12 @@ export class NavMenuComponent { const resourceId = this.route.firstChild?.snapshot.params['id'] || resourceFromQueryParams; const providerId = this.route.firstChild?.snapshot.params['providerId']; const isCollectionsWithId = urlSegments[0] === 'collections' && urlSegments[1] && urlSegments[1] !== ''; - const isRegistryRoute = urlSegments[0] === 'registries' && !!urlSegments[2]; const isPreprintRoute = urlSegments[0] === 'preprints' && !!urlSegments[2]; return { resourceId, providerId, isCollectionsWithId, - isRegistryRoute, isPreprintRoute, }; } diff --git a/src/app/core/constants/nav-items.constant.ts b/src/app/core/constants/nav-items.constant.ts index 746da2133..ad6efa0a9 100644 --- a/src/app/core/constants/nav-items.constant.ts +++ b/src/app/core/constants/nav-items.constant.ts @@ -30,14 +30,14 @@ export const PROJECT_MENU_ITEMS: MenuItem[] = [ label: 'navigation.files', routerLink: 'files', visible: true, - routerLinkActiveOptions: { exact: true }, + routerLinkActiveOptions: { exact: false }, }, { id: 'project-wiki', label: 'navigation.wiki', routerLink: 'wiki', visible: true, - routerLinkActiveOptions: { exact: true }, + routerLinkActiveOptions: { exact: false }, }, { id: 'project-registrations', @@ -58,7 +58,7 @@ export const PROJECT_MENU_ITEMS: MenuItem[] = [ label: 'navigation.analytics', routerLink: 'analytics', visible: true, - routerLinkActiveOptions: { exact: true }, + routerLinkActiveOptions: { exact: false }, }, { id: 'project-addons', @@ -106,7 +106,7 @@ export const REGISTRATION_MENU_ITEMS: MenuItem[] = [ label: 'navigation.files', routerLink: 'files', visible: true, - routerLinkActiveOptions: { exact: true }, + routerLinkActiveOptions: { exact: false }, }, { id: 'registration-resources', @@ -120,7 +120,7 @@ export const REGISTRATION_MENU_ITEMS: MenuItem[] = [ label: 'navigation.wiki', routerLink: 'wiki', visible: true, - routerLinkActiveOptions: { exact: true }, + routerLinkActiveOptions: { exact: false }, }, { id: 'registration-components', @@ -148,7 +148,7 @@ export const REGISTRATION_MENU_ITEMS: MenuItem[] = [ label: 'navigation.analytics', routerLink: 'analytics', visible: true, - routerLinkActiveOptions: { exact: true }, + routerLinkActiveOptions: { exact: false }, }, ]; diff --git a/src/app/core/constants/ngxs-states.constant.ts b/src/app/core/constants/ngxs-states.constant.ts index c16df5cb4..be225a80c 100644 --- a/src/app/core/constants/ngxs-states.constant.ts +++ b/src/app/core/constants/ngxs-states.constant.ts @@ -1,14 +1,10 @@ import { ProviderState } from '@core/store/provider'; import { UserState } from '@core/store/user'; import { FilesState } from '@osf/features/files/store'; -import { MeetingsState } from '@osf/features/meetings/store'; import { ProjectMetadataState } from '@osf/features/project/metadata/store'; import { ProjectOverviewState } from '@osf/features/project/overview/store'; import { RegistrationsState } from '@osf/features/project/registrations/store'; -import { AccountSettingsState } from '@osf/features/settings/account-settings/store/account-settings.state'; -import { DeveloperAppsState } from '@osf/features/settings/developer-apps/store'; -import { NotificationSubscriptionState } from '@osf/features/settings/notifications/store'; -import { AddonsState, InstitutionsState, WikiState } from '@osf/shared/stores'; +import { AddonsState, CurrentResourceState, InstitutionsState, WikiState } from '@osf/shared/stores'; import { LicensesState } from '@shared/stores/licenses'; import { MyResourcesState } from '@shared/stores/my-resources'; import { RegionsState } from '@shared/stores/regions'; @@ -19,15 +15,12 @@ export const STATES = [ ProviderState, MyResourcesState, InstitutionsState, - DeveloperAppsState, - AccountSettingsState, - NotificationSubscriptionState, ProjectOverviewState, WikiState, - MeetingsState, RegistrationsState, ProjectMetadataState, LicensesState, RegionsState, FilesState, + CurrentResourceState, ]; diff --git a/src/app/core/guards/index.ts b/src/app/core/guards/index.ts index a58470270..13807a9a8 100644 --- a/src/app/core/guards/index.ts +++ b/src/app/core/guards/index.ts @@ -1,2 +1,4 @@ export { authGuard } from './auth.guard'; +export { isProjectGuard } from './is-project.guard'; +export { isRegistryGuard } from './is-registry.guard'; export { redirectIfLoggedInGuard } from './redirect-if-logged-in.guard'; diff --git a/src/app/core/guards/is-project.guard.ts b/src/app/core/guards/is-project.guard.ts new file mode 100644 index 000000000..0f78310ef --- /dev/null +++ b/src/app/core/guards/is-project.guard.ts @@ -0,0 +1,67 @@ +import { Store } from '@ngxs/store'; + +import { map, switchMap } from 'rxjs/operators'; + +import { inject } from '@angular/core'; +import { CanMatchFn, Route, Router, UrlSegment } from '@angular/router'; + +import { CurrentResourceType } from '../../shared/enums'; +import { CurrentResourceSelectors, GetResource } from '../../shared/stores'; + +export const isProjectGuard: CanMatchFn = (route: Route, segments: UrlSegment[]) => { + const store = inject(Store); + const router = inject(Router); + + const id = segments[0]?.path; + + if (!id) { + return false; + } + + const currentResource = store.selectSnapshot(CurrentResourceSelectors.getCurrentResource); + + if (currentResource && currentResource.id === id) { + if (currentResource.type === CurrentResourceType.Projects && currentResource.parentId) { + router.navigate(['/', currentResource.parentId, 'files', id]); + return true; + } + + if (currentResource.type === CurrentResourceType.Preprints && currentResource.parentId) { + router.navigate(['/preprints', currentResource.parentId, id]); + return true; + } + + if (currentResource.type === CurrentResourceType.Users) { + router.navigate(['/profile', id]); + return false; + } + + return currentResource.type === CurrentResourceType.Projects; + } + + return store.dispatch(new GetResource(id)).pipe( + switchMap(() => store.select(CurrentResourceSelectors.getCurrentResource)), + map((resource) => { + if (!resource || resource.id !== id) { + return false; + } + + if (resource.type === CurrentResourceType.Projects && resource.parentId) { + router.navigate(['/', resource.parentId, 'files', id]); + return true; + } + + if (resource.type === CurrentResourceType.Preprints && resource.parentId) { + router.navigate(['/preprints', resource.parentId, id]); + return true; + } + + if (resource.type === CurrentResourceType.Users) { + router.navigate(['/user', id]); + return false; + } + + return resource.type === CurrentResourceType.Projects; + }) + ); +}; diff --git a/src/app/core/guards/is-registry.guard.ts b/src/app/core/guards/is-registry.guard.ts new file mode 100644 index 000000000..0f592b553 --- /dev/null +++ b/src/app/core/guards/is-registry.guard.ts @@ -0,0 +1,67 @@ +import { Store } from '@ngxs/store'; + +import { map, switchMap } from 'rxjs/operators'; + +import { inject } from '@angular/core'; +import { CanMatchFn, Route, Router, UrlSegment } from '@angular/router'; + +import { CurrentResourceType } from '../../shared/enums'; +import { CurrentResourceSelectors, GetResource } from '../../shared/stores'; + +export const isRegistryGuard: CanMatchFn = (route: Route, segments: UrlSegment[]) => { + const store = inject(Store); + const router = inject(Router); + + const id = segments[0]?.path; + + if (!id) { + return false; + } + + const currentResource = store.selectSnapshot(CurrentResourceSelectors.getCurrentResource); + + if (currentResource && currentResource.id === id) { + if (currentResource.type === CurrentResourceType.Registrations && currentResource.parentId) { + router.navigate(['/', currentResource.parentId, 'files', id]); + return true; + } + + if (currentResource.type === CurrentResourceType.Preprints && currentResource.parentId) { + router.navigate(['/preprints', currentResource.parentId, id]); + return true; + } + + if (currentResource.type === CurrentResourceType.Users) { + router.navigate(['/user', id]); + return false; + } + + return currentResource.type === CurrentResourceType.Registrations; + } + + return store.dispatch(new GetResource(id)).pipe( + switchMap(() => store.select(CurrentResourceSelectors.getCurrentResource)), + map((resource) => { + if (!resource || resource.id !== id) { + return false; + } + + if (resource.type === CurrentResourceType.Registrations && resource.parentId) { + router.navigate(['/', resource.parentId, 'files', id]); + return true; + } + + if (resource.type === CurrentResourceType.Preprints && resource.parentId) { + router.navigate(['/preprints', resource.parentId, id]); + return true; + } + + if (resource.type === CurrentResourceType.Users) { + router.navigate(['/profile', id]); + return false; + } + + return resource.type === CurrentResourceType.Registrations; + }) + ); +}; diff --git a/src/app/core/helpers/nav-menu.helper.ts b/src/app/core/helpers/nav-menu.helper.ts index 178988118..4620a5fad 100644 --- a/src/app/core/helpers/nav-menu.helper.ts +++ b/src/app/core/helpers/nav-menu.helper.ts @@ -88,7 +88,7 @@ function updateProjectMenuItem(item: MenuItem, ctx: RouteContext): MenuItem { expanded: true, items: PROJECT_MENU_ITEMS.map((menuItem) => ({ ...menuItem, - routerLink: ['project', ctx.resourceId as string, menuItem.routerLink], + routerLink: [ctx.resourceId as string, menuItem.routerLink], })), }; } @@ -111,7 +111,7 @@ function updateRegistryMenuItem(item: MenuItem, ctx: RouteContext): MenuItem { expanded: true, items: REGISTRATION_MENU_ITEMS.map((menuItem) => ({ ...menuItem, - routerLink: ['registries', ctx.resourceId as string, menuItem.routerLink], + routerLink: [ctx.resourceId as string, menuItem.routerLink], })), }; } diff --git a/src/app/features/collections/components/add-to-collection/add-to-collection.component.ts b/src/app/features/collections/components/add-to-collection/add-to-collection.component.ts index 8ba79ae69..7fc43af75 100644 --- a/src/app/features/collections/components/add-to-collection/add-to-collection.component.ts +++ b/src/app/features/collections/components/add-to-collection/add-to-collection.component.ts @@ -142,7 +142,7 @@ export class AddToCollectionComponent implements CanDeactivateComponent { dialogRef.onClose.subscribe((result) => { if (result) { this.allowNavigation.set(true); - this.router.navigate(['/project', this.selectedProject()?.id, 'overview']); + this.router.navigate([this.selectedProject()?.id, 'overview']); } }); } diff --git a/src/app/features/files/pages/file-detail/file-detail.component.ts b/src/app/features/files/pages/file-detail/file-detail.component.ts index 8a0c74f1b..c489c8a41 100644 --- a/src/app/features/files/pages/file-detail/file-detail.component.ts +++ b/src/app/features/files/pages/file-detail/file-detail.component.ts @@ -166,8 +166,7 @@ export class FileDetailComponent { deleteEntry(link: string): void { if (this.resourceId) { - const redirectUrl = - this.resourceType === 'nodes' ? `/project/${this.resourceId}/files` : `/registry/${this.resourceId}/files`; + const redirectUrl = `/${this.resourceId}/files`; this.actions .deleteEntry(this.resourceId, link) .pipe(takeUntilDestroyed(this.destroyRef)) diff --git a/src/app/features/home/pages/dashboard/dashboard.component.ts b/src/app/features/home/pages/dashboard/dashboard.component.ts index 66608b15f..e21e27536 100644 --- a/src/app/features/home/pages/dashboard/dashboard.component.ts +++ b/src/app/features/home/pages/dashboard/dashboard.component.ts @@ -222,7 +222,7 @@ export class DashboardComponent implements OnInit { protected navigateToProject(project: MyResourcesItem): void { this.activeProject.set(project); - this.router.navigate(['/project', project.id]); + this.router.navigate([project.id]); } protected createProject(): void { diff --git a/src/app/features/meetings/meetings.routes.ts b/src/app/features/meetings/meetings.routes.ts index fd4a398d0..b0f7a5701 100644 --- a/src/app/features/meetings/meetings.routes.ts +++ b/src/app/features/meetings/meetings.routes.ts @@ -1,11 +1,15 @@ +import { provideStates } from '@ngxs/store'; + import { Routes } from '@angular/router'; +import { MeetingsState } from './store/meetings.state'; import { MeetingsComponent } from './meetings.component'; export const meetingsRoutes: Routes = [ { path: '', component: MeetingsComponent, + providers: [provideStates([MeetingsState])], children: [ { path: '', diff --git a/src/app/features/meetings/pages/meeting-details/meeting-details.component.html b/src/app/features/meetings/pages/meeting-details/meeting-details.component.html index 9cd5d4006..5646f45f1 100644 --- a/src/app/features/meetings/pages/meeting-details/meeting-details.component.html +++ b/src/app/features/meetings/pages/meeting-details/meeting-details.component.html @@ -57,7 +57,7 @@ @if (item?.id) { - + {{ item.title }} {{ item.authorName }} {{ item.meetingCategory }} diff --git a/src/app/features/moderation/components/index.ts b/src/app/features/moderation/components/index.ts index 42b5e359e..4884d20bb 100644 --- a/src/app/features/moderation/components/index.ts +++ b/src/app/features/moderation/components/index.ts @@ -7,10 +7,10 @@ export { ModeratorsTableComponent } from './moderators-table/moderators-table.co export { MyReviewingNavigationComponent } from './my-reviewing-navigation/my-reviewing-navigation.component'; export { NotificationSettingsComponent } from './notification-settings/notification-settings.component'; export { PreprintModerationSettingsComponent } from './preprint-moderation-settings/preprint-moderation-settings.component'; +export { PreprintRecentActivityListComponent } from './preprint-recent-activity-list/preprint-recent-activity-list.component'; export { PreprintSubmissionItemComponent } from './preprint-submission-item/preprint-submission-item.component'; export { PreprintSubmissionsComponent } from './preprint-submissions/preprint-submissions.component'; export { PreprintWithdrawalSubmissionsComponent } from './preprint-withdrawal-submissions/preprint-withdrawal-submissions.component'; -export { RecentActivityListComponent } from './recent-activity-list/recent-activity-list.component'; export { RegistryPendingSubmissionsComponent } from './registry-pending-submissions/registry-pending-submissions.component'; export { RegistrySettingsComponent } from './registry-settings/registry-settings.component'; export { RegistrySubmissionItemComponent } from './registry-submission-item/registry-submission-item.component'; diff --git a/src/app/features/moderation/components/recent-activity-list/recent-activity-list.component.html b/src/app/features/moderation/components/preprint-recent-activity-list/preprint-recent-activity-list.component.html similarity index 100% rename from src/app/features/moderation/components/recent-activity-list/recent-activity-list.component.html rename to src/app/features/moderation/components/preprint-recent-activity-list/preprint-recent-activity-list.component.html diff --git a/src/app/features/moderation/components/recent-activity-list/recent-activity-list.component.scss b/src/app/features/moderation/components/preprint-recent-activity-list/preprint-recent-activity-list.component.scss similarity index 100% rename from src/app/features/moderation/components/recent-activity-list/recent-activity-list.component.scss rename to src/app/features/moderation/components/preprint-recent-activity-list/preprint-recent-activity-list.component.scss diff --git a/src/app/features/moderation/components/recent-activity-list/recent-activity-list.component.spec.ts b/src/app/features/moderation/components/preprint-recent-activity-list/preprint-recent-activity-list.component.spec.ts similarity index 57% rename from src/app/features/moderation/components/recent-activity-list/recent-activity-list.component.spec.ts rename to src/app/features/moderation/components/preprint-recent-activity-list/preprint-recent-activity-list.component.spec.ts index d07ee1d06..b3332b1ef 100644 --- a/src/app/features/moderation/components/recent-activity-list/recent-activity-list.component.spec.ts +++ b/src/app/features/moderation/components/preprint-recent-activity-list/preprint-recent-activity-list.component.spec.ts @@ -5,25 +5,25 @@ import { DatePipe } from '@angular/common'; import { ComponentRef } from '@angular/core'; import { ComponentFixture, TestBed } from '@angular/core/testing'; -import { CustomPaginatorComponent, IconComponent } from '@shared/components'; +import { CustomPaginatorComponent, IconComponent } from '@osf/shared/components'; -import { RecentActivityListComponent } from './recent-activity-list.component'; +import { PreprintRecentActivityListComponent } from './preprint-recent-activity-list.component'; -describe('RecentActivityListComponent', () => { - let component: RecentActivityListComponent; - let componentRef: ComponentRef; - let fixture: ComponentFixture; +describe('PreprintRecentActivityListComponent', () => { + let component: PreprintRecentActivityListComponent; + let componentRef: ComponentRef; + let fixture: ComponentFixture; beforeEach(async () => { await TestBed.configureTestingModule({ imports: [ - RecentActivityListComponent, + PreprintRecentActivityListComponent, ...MockComponents(IconComponent, CustomPaginatorComponent), MockPipes(TranslatePipe, DatePipe), ], }).compileComponents(); - fixture = TestBed.createComponent(RecentActivityListComponent); + fixture = TestBed.createComponent(PreprintRecentActivityListComponent); component = fixture.componentInstance; componentRef = fixture.componentRef; diff --git a/src/app/features/moderation/components/recent-activity-list/recent-activity-list.component.ts b/src/app/features/moderation/components/preprint-recent-activity-list/preprint-recent-activity-list.component.ts similarity index 77% rename from src/app/features/moderation/components/recent-activity-list/recent-activity-list.component.ts rename to src/app/features/moderation/components/preprint-recent-activity-list/preprint-recent-activity-list.component.ts index 271641812..5e32a5e2b 100644 --- a/src/app/features/moderation/components/recent-activity-list/recent-activity-list.component.ts +++ b/src/app/features/moderation/components/preprint-recent-activity-list/preprint-recent-activity-list.component.ts @@ -8,18 +8,17 @@ import { DatePipe } from '@angular/common'; import { ChangeDetectionStrategy, Component, input, output, signal } from '@angular/core'; import { PreprintReviewStatus, ReviewStatusIcon } from '@osf/features/moderation/constants'; +import { PreprintReviewActionModel } from '@osf/features/moderation/models'; import { CustomPaginatorComponent, IconComponent } from '@osf/shared/components'; -import { PreprintReviewActionModel } from '../../models'; - @Component({ - selector: 'osf-recent-activity-list', + selector: 'osf-preprint-recent-activity-list', imports: [TableModule, DatePipe, TranslatePipe, IconComponent, Skeleton, CustomPaginatorComponent], - templateUrl: './recent-activity-list.component.html', - styleUrl: './recent-activity-list.component.scss', + templateUrl: './preprint-recent-activity-list.component.html', + styleUrl: './preprint-recent-activity-list.component.scss', changeDetection: ChangeDetectionStrategy.OnPush, }) -export class RecentActivityListComponent { +export class PreprintRecentActivityListComponent { reviews = input.required(); isLoading = input(false); totalCount = input(0); diff --git a/src/app/features/moderation/components/registry-submission-item/registry-submission-item.component.html b/src/app/features/moderation/components/registry-submission-item/registry-submission-item.component.html index ce8e9621b..55c570e5c 100644 --- a/src/app/features/moderation/components/registry-submission-item/registry-submission-item.component.html +++ b/src/app/features/moderation/components/registry-submission-item/registry-submission-item.component.html @@ -6,7 +6,7 @@ class="link-btn-no-padding" link [label]="submission().title" - [routerLink]="['/registries/', submission().id, 'overview']" + [routerLink]="[submission().id, 'overview']" [queryParams]="{ mode: 'moderator', revisionId: isPendingModeration && !isPending ? submission().revisionId : null, diff --git a/src/app/features/moderation/pages/my-preprint-reviewing/my-preprint-reviewing.component.html b/src/app/features/moderation/pages/my-preprint-reviewing/my-preprint-reviewing.component.html index f2d2847a9..f82def47c 100644 --- a/src/app/features/moderation/pages/my-preprint-reviewing/my-preprint-reviewing.component.html +++ b/src/app/features/moderation/pages/my-preprint-reviewing/my-preprint-reviewing.component.html @@ -6,12 +6,12 @@

{{ 'project.overview.recentActivity.title' | translate }}

- + >
diff --git a/src/app/features/moderation/pages/my-preprint-reviewing/my-preprint-reviewing.component.spec.ts b/src/app/features/moderation/pages/my-preprint-reviewing/my-preprint-reviewing.component.spec.ts index 729e2a9bf..26ff68aeb 100644 --- a/src/app/features/moderation/pages/my-preprint-reviewing/my-preprint-reviewing.component.spec.ts +++ b/src/app/features/moderation/pages/my-preprint-reviewing/my-preprint-reviewing.component.spec.ts @@ -8,7 +8,7 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; import { SubHeaderComponent } from '@osf/shared/components'; import { MOCK_STORE } from '@shared/mocks'; -import { MyReviewingNavigationComponent, RecentActivityListComponent } from '../../components'; +import { MyReviewingNavigationComponent, PreprintRecentActivityListComponent } from '../../components'; import { PreprintModerationSelectors } from '../../store/preprint-moderation'; import { MyPreprintReviewingComponent } from './my-preprint-reviewing.component'; @@ -30,7 +30,7 @@ describe('MyPreprintReviewingComponent', () => { await TestBed.configureTestingModule({ imports: [ MyPreprintReviewingComponent, - ...MockComponents(SubHeaderComponent, MyReviewingNavigationComponent, RecentActivityListComponent), + ...MockComponents(SubHeaderComponent, MyReviewingNavigationComponent, PreprintRecentActivityListComponent), MockPipe(TranslatePipe), ], providers: [MockProvider(Store, MOCK_STORE)], diff --git a/src/app/features/moderation/pages/my-preprint-reviewing/my-preprint-reviewing.component.ts b/src/app/features/moderation/pages/my-preprint-reviewing/my-preprint-reviewing.component.ts index 9a7350ea9..73e2146c5 100644 --- a/src/app/features/moderation/pages/my-preprint-reviewing/my-preprint-reviewing.component.ts +++ b/src/app/features/moderation/pages/my-preprint-reviewing/my-preprint-reviewing.component.ts @@ -9,7 +9,7 @@ import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core'; import { SubHeaderComponent } from '@osf/shared/components'; -import { MyReviewingNavigationComponent, RecentActivityListComponent } from '../../components'; +import { MyReviewingNavigationComponent, PreprintRecentActivityListComponent } from '../../components'; import { GetPreprintProviders, GetPreprintReviewActions, @@ -23,7 +23,7 @@ import { Card, Skeleton, TranslatePipe, - RecentActivityListComponent, + PreprintRecentActivityListComponent, MyReviewingNavigationComponent, ], templateUrl: './my-preprint-reviewing.component.html', diff --git a/src/app/features/my-projects/my-projects.component.ts b/src/app/features/my-projects/my-projects.component.ts index 615d93d3d..0c4fd2404 100644 --- a/src/app/features/my-projects/my-projects.component.ts +++ b/src/app/features/my-projects/my-projects.component.ts @@ -343,11 +343,11 @@ export class MyProjectsComponent implements OnInit { protected navigateToProject(project: MyResourcesItem): void { this.activeProject.set(project); - this.router.navigate(['/project', project.id]); + this.router.navigate([project.id]); } protected navigateToRegistry(registry: MyResourcesItem): void { this.activeProject.set(registry); - this.router.navigate(['/registries', registry.id]); + this.router.navigate([registry.id]); } } diff --git a/src/app/features/project/analytics/analytics.component.ts b/src/app/features/project/analytics/analytics.component.ts index fed29a25c..3981f063e 100644 --- a/src/app/features/project/analytics/analytics.component.ts +++ b/src/app/features/project/analytics/analytics.component.ts @@ -21,7 +21,6 @@ import { AnalyticsKpiComponent } from './components'; import { DATE_RANGE_OPTIONS } from './constants'; import { DateRangeOption } from './models'; import { AnalyticsSelectors, ClearAnalytics, GetMetrics, GetRelatedCounts } from './store'; -import { analyticsData } from './test-data'; @Component({ selector: 'osf-analytics', @@ -105,7 +104,7 @@ export class AnalyticsComponent implements OnInit { } private setData() { - const analytics = this.analytics() || analyticsData; + const analytics = this.analytics(); if (!analytics) { return; diff --git a/src/app/features/project/analytics/components/view-duplicates/view-duplicates.component.html b/src/app/features/project/analytics/components/view-duplicates/view-duplicates.component.html index f3940f870..4db891274 100644 --- a/src/app/features/project/analytics/components/view-duplicates/view-duplicates.component.html +++ b/src/app/features/project/analytics/components/view-duplicates/view-duplicates.component.html @@ -69,11 +69,7 @@

- + } } diff --git a/src/app/features/project/analytics/components/view-duplicates/view-duplicates.component.ts b/src/app/features/project/analytics/components/view-duplicates/view-duplicates.component.ts index 03596d19b..d41d472cc 100644 --- a/src/app/features/project/analytics/components/view-duplicates/view-duplicates.component.ts +++ b/src/app/features/project/analytics/components/view-duplicates/view-duplicates.component.ts @@ -81,11 +81,11 @@ export class ViewDuplicatesComponent { protected readonly forkActionItems = (resourceId: string) => [ { label: 'project.overview.actions.manageContributors', - command: () => this.router.navigate(['/project', resourceId, 'contributors']), + command: () => this.router.navigate([resourceId, 'contributors']), }, { label: 'project.overview.actions.settings', - command: () => this.router.navigate(['/project', resourceId, 'settings']), + command: () => this.router.navigate([resourceId, 'settings']), }, { label: 'project.overview.actions.delete', diff --git a/src/app/features/project/analytics/services/analytics.service.ts b/src/app/features/project/analytics/services/analytics.service.ts index 786b84c6e..b9f358ae2 100644 --- a/src/app/features/project/analytics/services/analytics.service.ts +++ b/src/app/features/project/analytics/services/analytics.service.ts @@ -26,7 +26,7 @@ export class AnalyticsService { const baseUrl = `${environment.apiDomainUrl}/_/metrics/query/node_analytics`; return this.jsonApiService - .get>(`${baseUrl}/${resourceId}/${dateRange}`) + .get>(`${baseUrl}/${resourceId}/${dateRange}/`) .pipe(map((response) => AnalyticsMetricsMapper.fromResponse(response.data))); } diff --git a/src/app/features/project/analytics/test-data.ts b/src/app/features/project/analytics/test-data.ts deleted file mode 100644 index 61472c1b9..000000000 --- a/src/app/features/project/analytics/test-data.ts +++ /dev/null @@ -1,135 +0,0 @@ -export const analyticsData = { - popularPages: [ - { - path: '/4znzp', - route: 'OsfWebRenderer.view_project', - title: 'OSF | OSF', - count: 246, - }, - { - path: '/4znzp/wiki/home', - route: 'OsfWebRenderer.project_wiki_view', - title: 'OSF | OSF Wiki', - count: 147, - }, - { - path: '/4znzp/files/osfstorage', - route: 'ember-osf-web.guid-node.files.provider', - title: 'OSF', - count: 37, - }, - { - path: '/4znzp/wiki/Making%20work%20citable%20using%20the%20OSF', - route: 'OsfWebRenderer.project_wiki_view', - title: 'OSF | OSF Wiki', - count: 29, - }, - { - path: '/4znzp/wiki/OSF%20for%20Researchers', - route: 'OsfWebRenderer.project_wiki_view', - title: 'OSF | OSF Wiki', - count: 28, - }, - { - path: '/4znzp/wiki/OSF%20for%20Journals', - route: 'OsfWebRenderer.project_wiki_view', - title: 'OSF | OSF Wiki', - count: 23, - }, - { - path: '/4znzp/analytics', - route: 'ember-osf-web.guid-node.analytics.index', - title: 'OSF', - count: 13, - }, - { - path: '/4znzp/metadata/osf', - route: 'ember-osf-web.guid-node.metadata.detail', - title: 'OSF | OSF | Metadata', - count: 11, - }, - { - path: '/4znzp/wiki/Open%20Science%20Projects', - route: 'OsfWebRenderer.project_wiki_view', - title: 'OSF | OSF Wiki', - count: 11, - }, - { - path: '/4znzp/registrations', - route: 'ember-osf-web.guid-node.registrations', - title: 'OSF', - count: 8, - }, - ], - uniqueVisits: [ - { date: '2025-04-16', count: 12 }, - { date: '2025-04-17', count: 18 }, - { date: '2025-04-18', count: 6 }, - { date: '2025-04-19', count: 12 }, - { date: '2025-04-20', count: 6 }, - { date: '2025-04-21', count: 17 }, - { date: '2025-04-22', count: 26 }, - { date: '2025-04-23', count: 26 }, - { date: '2025-04-24', count: 33 }, - { date: '2025-04-25', count: 11 }, - { date: '2025-04-26', count: 18 }, - { date: '2025-04-27', count: 10 }, - { date: '2025-04-28', count: 17 }, - { date: '2025-04-29', count: 30 }, - { date: '2025-04-30', count: 36 }, - { date: '2025-05-01', count: 29 }, - { date: '2025-05-02', count: 23 }, - { date: '2025-05-03', count: 10 }, - { date: '2025-05-04', count: 16 }, - { date: '2025-05-05', count: 19 }, - { date: '2025-05-06', count: 24 }, - { date: '2025-05-07', count: 32 }, - { date: '2025-05-08', count: 17 }, - { date: '2025-05-09', count: 29 }, - { date: '2025-05-10', count: 8 }, - { date: '2025-05-11', count: 13 }, - { date: '2025-05-12', count: 24 }, - { date: '2025-05-13', count: 12 }, - { date: '2025-05-14', count: 30 }, - { date: '2025-05-15', count: 20 }, - { date: '2025-05-16', count: 13 }, - ], - timeOfDay: [ - { hour: 18, count: 52 }, - { hour: 15, count: 41 }, - { hour: 6, count: 38 }, - { hour: 10, count: 32 }, - { hour: 14, count: 30 }, - { hour: 7, count: 29 }, - { hour: 19, count: 29 }, - { hour: 13, count: 27 }, - { hour: 17, count: 27 }, - { hour: 9, count: 26 }, - { hour: 11, count: 26 }, - { hour: 8, count: 23 }, - { hour: 16, count: 23 }, - { hour: 1, count: 22 }, - { hour: 2, count: 22 }, - { hour: 21, count: 22 }, - { hour: 4, count: 21 }, - { hour: 20, count: 20 }, - { hour: 0, count: 19 }, - { hour: 12, count: 18 }, - { hour: 22, count: 18 }, - { hour: 23, count: 17 }, - { hour: 5, count: 12 }, - { hour: 3, count: 3 }, - ], - refererDomain: [ - { refererDomain: 'www.google.com', count: 290 }, - { refererDomain: 'osf.io', count: 175 }, - { refererDomain: 'orcid.org', count: 8 }, - { refererDomain: 'accounts.osf.io', count: 6 }, - { refererDomain: 'www.ecosia.org', count: 5 }, - { refererDomain: 'duckduckgo.com', count: 4 }, - { refererDomain: 'www.cos.io', count: 4 }, - { refererDomain: 'www.bing.com', count: 3 }, - { refererDomain: 'www.google.com.hk', count: 3 }, - { refererDomain: 'arca-dpss.github.io', count: 2 }, - ], -}; diff --git a/src/app/features/project/overview/components/add-component-dialog/add-component-dialog.component.html b/src/app/features/project/overview/components/add-component-dialog/add-component-dialog.component.html index 664cd9c6a..6e3110095 100644 --- a/src/app/features/project/overview/components/add-component-dialog/add-component-dialog.component.html +++ b/src/app/features/project/overview/components/add-component-dialog/add-component-dialog.component.html @@ -42,7 +42,7 @@

[inputId]="affiliation.id" [name]="'affiliations'" /> - OSF Logo + Institution Logo } @@ -79,7 +79,6 @@

-

-

diff --git a/src/app/features/project/overview/components/add-component-dialog/add-component-dialog.component.scss b/src/app/features/project/overview/components/add-component-dialog/add-component-dialog.component.scss index b32edb90d..5bd067ff2 100644 --- a/src/app/features/project/overview/components/add-component-dialog/add-component-dialog.component.scss +++ b/src/app/features/project/overview/components/add-component-dialog/add-component-dialog.component.scss @@ -1,6 +1,4 @@ -@use "/assets/styles/variables" as var; - .affiliation-label { text-transform: none; - color: var.$grey-1; + color: var(--grey-1); } diff --git a/src/app/features/project/overview/components/linked-resources/linked-resources.component.html b/src/app/features/project/overview/components/linked-resources/linked-resources.component.html index 240745ac9..26583a283 100644 --- a/src/app/features/project/overview/components/linked-resources/linked-resources.component.html +++ b/src/app/features/project/overview/components/linked-resources/linked-resources.component.html @@ -19,7 +19,7 @@

{{ 'project.overview.linkedProjects.title' | translate }}

- {{ linkedResource.title }} + {{ linkedResource.title }}

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 5496a16f5..c5528e311 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 @@ -11,7 +11,7 @@ import { toSignal } from '@angular/core/rxjs-interop'; import { DeleteNodeLinkDialogComponent, LinkResourceDialogComponent } from '@osf/features/project/overview/components'; import { IconComponent, TruncatedTextComponent } from '@osf/shared/components'; -import { IS_XSMALL } from '@osf/shared/helpers'; +import { IS_MEDIUM } from '@osf/shared/helpers'; import { NodeLinksSelectors } from '@shared/stores'; @Component({ @@ -25,14 +25,16 @@ import { NodeLinksSelectors } from '@shared/stores'; export class LinkedResourcesComponent { private dialogService = inject(DialogService); private translateService = inject(TranslateService); + isCollectionsRoute = input(false); canWrite = input.required(); + protected linkedResources = select(NodeLinksSelectors.getLinkedResources); protected isLinkedResourcesLoading = select(NodeLinksSelectors.getLinkedResourcesLoading); - protected isMobile = toSignal(inject(IS_XSMALL)); + protected isMedium = toSignal(inject(IS_MEDIUM)); openLinkProjectModal() { - const dialogWidth = this.isMobile() ? '95vw' : '850px'; + const dialogWidth = this.isMedium() ? '850px' : '95vw'; this.dialogService.open(LinkResourceDialogComponent, { width: dialogWidth, @@ -45,7 +47,7 @@ export class LinkedResourcesComponent { } openDeleteResourceModal(resourceId: string): void { - const dialogWidth = this.isMobile() ? '95vw' : '650px'; + const dialogWidth = this.isMedium() ? '650px' : '95vw'; const currentLink = this.getCurrentResourceNodeLink(resourceId); diff --git a/src/app/features/project/overview/components/overview-components/overview-components.component.html b/src/app/features/project/overview/components/overview-components/overview-components.component.html index 275c3c19c..7c208c1dd 100644 --- a/src/app/features/project/overview/components/overview-components/overview-components.component.html +++ b/src/app/features/project/overview/components/overview-components/overview-components.component.html @@ -21,7 +21,7 @@

{{ 'project.overview.components.title' | translate }}

- {{ component.title }} + {{ component.title }}

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 01660ddfd..de6dca821 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 @@ -38,11 +38,11 @@ export class OverviewComponentsComponent { protected readonly componentActionItems = (componentId: string) => [ { label: 'project.overview.actions.manageContributors', - command: () => this.router.navigate(['/project', componentId, 'contributors']), + command: () => this.router.navigate([componentId, 'contributors']), }, { label: 'project.overview.actions.settings', - command: () => this.router.navigate(['/project', componentId, 'settings']), + command: () => this.router.navigate([componentId, 'settings']), }, { label: 'project.overview.actions.delete', diff --git a/src/app/features/project/overview/components/overview-toolbar/overview-toolbar.component.html b/src/app/features/project/overview/components/overview-toolbar/overview-toolbar.component.html index a5b60302c..dad385b37 100644 --- a/src/app/features/project/overview/components/overview-toolbar/overview-toolbar.component.html +++ b/src/app/features/project/overview/components/overview-toolbar/overview-toolbar.component.html @@ -46,7 +46,7 @@ class="flex" [pTooltip]="'project.overview.tooltips.viewOnlyLinks' | translate" tooltipPosition="bottom" - [routerLink]="['/project', resource.id, 'settings']" + [routerLink]="[resource.id, 'settings']" > {{ resource.viewOnlyLinksCount }} diff --git a/src/app/features/project/overview/components/recent-activity/recent-activity.component.html b/src/app/features/project/overview/components/recent-activity/recent-activity.component.html index 09bacf33f..ef289226b 100644 --- a/src/app/features/project/overview/components/recent-activity/recent-activity.component.html +++ b/src/app/features/project/overview/components/recent-activity/recent-activity.component.html @@ -6,7 +6,7 @@

{{ 'project.overview.recentActivity.title' | translate }}

@for (activityLog of formattedActivityLogs(); track activityLog.id) {
- +
} } @else { diff --git a/src/app/features/project/overview/components/recent-activity/recent-activity.component.scss b/src/app/features/project/overview/components/recent-activity/recent-activity.component.scss index 800d08df0..5463c04b2 100644 --- a/src/app/features/project/overview/components/recent-activity/recent-activity.component.scss +++ b/src/app/features/project/overview/components/recent-activity/recent-activity.component.scss @@ -1,13 +1,11 @@ -@use "/assets/styles/variables" as var; @use "/assets/styles/mixins" as mix; .activities { - border: 1px solid var.$grey-2; + border: 1px solid var(--grey-2); border-radius: mix.rem(12px); - color: var.$dark-blue-1; &-activity { - border-bottom: 1px solid var.$grey-2; + border-bottom: 1px solid var(--grey-2); .activity-date { width: 30%; diff --git a/src/app/features/project/project.routes.ts b/src/app/features/project/project.routes.ts index 9ae5e0bc0..5633aaf06 100644 --- a/src/app/features/project/project.routes.ts +++ b/src/app/features/project/project.routes.ts @@ -15,6 +15,8 @@ import { } from '@osf/shared/stores'; import { ActivityLogsState } from '@shared/stores/activity-logs'; +import { NotificationSubscriptionState } from '../settings/notifications/store'; + import { AnalyticsState } from './analytics/store'; import { SettingsState } from './settings/store'; @@ -61,7 +63,7 @@ export const projectRoutes: Routes = [ { path: 'settings', loadComponent: () => import('../project/settings/settings.component').then((mod) => mod.SettingsComponent), - providers: [provideStates([SettingsState, ViewOnlyLinkState])], + providers: [provideStates([SettingsState, ViewOnlyLinkState, NotificationSubscriptionState])], }, { path: 'contributors', diff --git a/src/app/features/project/settings/components/project-detail-setting-accordion/project-detail-setting-accordion.component.html b/src/app/features/project/settings/components/project-detail-setting-accordion/project-detail-setting-accordion.component.html index 833182fd1..e5060ec04 100644 --- a/src/app/features/project/settings/components/project-detail-setting-accordion/project-detail-setting-accordion.component.html +++ b/src/app/features/project/settings/components/project-detail-setting-accordion/project-detail-setting-accordion.component.html @@ -15,7 +15,7 @@ @if (rightControls()) {
- @for (control of rightControls(); let index = $index; track control.value) { + @for (control of rightControls(); let index = $index; track index) {
@if (control.label) { diff --git a/src/app/features/registry/pages/registry-components/registry-components.component.ts b/src/app/features/registry/pages/registry-components/registry-components.component.ts index 9f75cf25d..2edd5e28d 100644 --- a/src/app/features/registry/pages/registry-components/registry-components.component.ts +++ b/src/app/features/registry/pages/registry-components/registry-components.component.ts @@ -95,6 +95,6 @@ export class RegistryComponentsComponent implements OnInit { } reviewComponentDetails(id: string): void { - this.router.navigate(['/registries', id, 'overview']); + this.router.navigate([id, 'overview']); } } diff --git a/src/app/features/registry/pages/registry-links/registry-links.component.ts b/src/app/features/registry/pages/registry-links/registry-links.component.ts index 7ce33628a..e0e725b14 100644 --- a/src/app/features/registry/pages/registry-links/registry-links.component.ts +++ b/src/app/features/registry/pages/registry-links/registry-links.component.ts @@ -140,7 +140,7 @@ export class RegistryLinksComponent implements OnInit { } navigateToRegistrations(id: string): void { - this.router.navigate(['/registries', id, 'overview']); + this.router.navigate([id, 'overview']); } updateRegistration(id: string): void { @@ -157,7 +157,7 @@ export class RegistryLinksComponent implements OnInit { } navigateToNodes(id: string): void { - this.router.navigate(['/project', id, 'overview']); + this.router.navigate([id, 'overview']); } fetchContributors(nodeId: string): void { 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 65cc97c19..937cba632 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 @@ -60,7 +60,7 @@ import { templateUrl: './registry-overview.component.html', styleUrl: './registry-overview.component.scss', changeDetection: ChangeDetectionStrategy.OnPush, - providers: [DialogService, DatePipe], + providers: [DialogService], }) export class RegistryOverviewComponent { @HostBinding('class') classes = 'flex-1 flex flex-column w-full h-full'; @@ -70,7 +70,6 @@ export class RegistryOverviewComponent { private readonly toastService = inject(ToastService); private readonly dialogService = inject(DialogService); private readonly translateService = inject(TranslateService); - private readonly datePipe = inject(DatePipe); protected readonly registry = select(RegistryOverviewSelectors.getRegistry); protected readonly isRegistryLoading = select(RegistryOverviewSelectors.isRegistryLoading); diff --git a/src/app/features/registry/registry.routes.ts b/src/app/features/registry/registry.routes.ts index 398be1c9e..73c66b43b 100644 --- a/src/app/features/registry/registry.routes.ts +++ b/src/app/features/registry/registry.routes.ts @@ -5,12 +5,22 @@ import { Routes } from '@angular/router'; import { RegistryComponentsState } from '@osf/features/registry/store/registry-components'; import { RegistryLinksState } from '@osf/features/registry/store/registry-links'; import { RegistryMetadataState } from '@osf/features/registry/store/registry-metadata'; -import { RegistryOverviewState } from '@osf/features/registry/store/registry-overview'; import { ResourceType } from '@osf/shared/enums'; -import { ContributorsState, DuplicatesState, ViewOnlyLinkState } from '@osf/shared/stores'; +import { LicensesService } from '@osf/shared/services'; +import { + CitationsState, + ContributorsState, + DuplicatesState, + SubjectsState, + ViewOnlyLinkState, +} from '@osf/shared/stores'; import { AnalyticsState } from '../project/analytics/store'; +import { RegistriesState } from '../registries/store'; +import { LicensesHandlers, ProjectsHandlers, ProvidersHandlers } from '../registries/store/handlers'; +import { FilesHandlers } from '../registries/store/handlers/files.handlers'; +import { RegistryOverviewState } from './store/registry-overview'; import { RegistryResourcesState } from './store/registry-resources/registry-resources.state'; import { RegistryComponent } from './registry.component'; @@ -29,12 +39,20 @@ export const registryRoutes: Routes = [ path: 'overview', loadComponent: () => import('./pages/registry-overview/registry-overview.component').then((c) => c.RegistryOverviewComponent), + providers: [ + provideStates([RegistriesState, CitationsState]), + ProvidersHandlers, + ProjectsHandlers, + LicensesHandlers, + FilesHandlers, + LicensesService, + ], }, { path: 'metadata', loadComponent: () => import('./pages/registry-metadata/registry-metadata.component').then((c) => c.RegistryMetadataComponent), - providers: [provideStates([RegistryMetadataState])], + providers: [provideStates([RegistryMetadataState, SubjectsState])], }, { path: 'metadata/add', diff --git a/src/app/features/settings/developer-apps/developer-apps.route.ts b/src/app/features/settings/developer-apps/developer-apps.route.ts index a5b8dda1d..30f0ace4d 100644 --- a/src/app/features/settings/developer-apps/developer-apps.route.ts +++ b/src/app/features/settings/developer-apps/developer-apps.route.ts @@ -1,10 +1,14 @@ +import { provideStates } from '@ngxs/store'; + import { Route } from '@angular/router'; import { DeveloperAppsContainerComponent } from './developer-apps-container.component'; +import { DeveloperAppsState } from './store'; export const developerAppsRoute: Route = { path: 'developer-apps', component: DeveloperAppsContainerComponent, + providers: [provideStates([DeveloperAppsState])], children: [ { path: '', diff --git a/src/app/features/settings/settings.routes.ts b/src/app/features/settings/settings.routes.ts index e6936cf8a..dc349dbd1 100644 --- a/src/app/features/settings/settings.routes.ts +++ b/src/app/features/settings/settings.routes.ts @@ -1,6 +1,10 @@ +import { provideStates } from '@ngxs/store'; + import { Routes } from '@angular/router'; +import { AccountSettingsState } from './account-settings/store'; import { developerAppsRoute } from './developer-apps/developer-apps.route'; +import { NotificationSubscriptionState } from './notifications/store'; import { tokensAppsRoute } from './tokens/tokens.route'; import { SettingsContainerComponent } from './settings-container.component'; @@ -8,6 +12,7 @@ export const settingsRoutes: Routes = [ { path: '', component: SettingsContainerComponent, + providers: [provideStates([AccountSettingsState])], children: [ { path: '', @@ -46,6 +51,7 @@ export const settingsRoutes: Routes = [ path: 'notifications', loadComponent: () => import('./notifications/notifications.component').then((mod) => mod.NotificationsComponent), + providers: [provideStates([NotificationSubscriptionState])], }, ], }, diff --git a/src/app/shared/components/data-resources/data-resources.component.html b/src/app/shared/components/data-resources/data-resources.component.html index 94c4421a6..48a878e97 100644 --- a/src/app/shared/components/data-resources/data-resources.component.html +++ b/src/app/shared/components/data-resources/data-resources.component.html @@ -1,5 +1,5 @@
- + data-resource{{ 'resourceCard.resources.data' | translate }}

- + code-resource{{ 'resourceCard.resources.analyticCode' | translate }}

- + materials-resource{{ 'resourceCard.resources.materials' | translate }}

- + papers-resource{{ 'resourceCard.resources.papers' | translate }}

- + supplements-resource(); hasSupplements = input(); - getResourceLink(): string { - return '/registries/' + this.resourceId() + '/resources'; + get resourceLink(): string { + return `/${this.resourceId()}/resources`; } } diff --git a/src/app/shared/components/registration-card/registration-card.component.html b/src/app/shared/components/registration-card/registration-card.component.html index a9967231b..057e02cf3 100644 --- a/src/app/shared/components/registration-card/registration-card.component.html +++ b/src/app/shared/components/registration-card/registration-card.component.html @@ -73,7 +73,7 @@

} @else { diff --git a/src/app/shared/components/resource-card/resource-card.component.ts b/src/app/shared/components/resource-card/resource-card.component.ts index 6f6a2d362..d422f8475 100644 --- a/src/app/shared/components/resource-card/resource-card.component.ts +++ b/src/app/shared/components/resource-card/resource-card.component.ts @@ -80,7 +80,7 @@ export class ResourceCardComponent { if (item.resourceType === ResourceType.Registration) { const parts = item.id.split('/'); const uri = parts[parts.length - 1]; - this.router.navigate(['/registries', uri]); + this.router.navigate([uri]); } } } diff --git a/src/app/shared/components/wiki/wiki-list/wiki-list.component.html b/src/app/shared/components/wiki/wiki-list/wiki-list.component.html index 4d77cf662..cf8b708ba 100644 --- a/src/app/shared/components/wiki/wiki-list/wiki-list.component.html +++ b/src/app/shared/components/wiki/wiki-list/wiki-list.component.html @@ -1,4 +1,4 @@ -
+
@if (isLoading()) { @@ -13,12 +13,12 @@
@@ -37,7 +37,7 @@ [label]="'common.buttons.delete' | translate" severity="danger" outlined - (click)="openDeleteWikiDialog()" + (onClick)="openDeleteWikiDialog()" class="mb-2 flex" > @@ -51,17 +51,17 @@ @case (wikiItemType.Folder) { -

{{ item.label }}

+

{{ item.label | translate }}

} @case (wikiItemType.Component) { - {{ item.label }} + {{ item.label | translate }} } @default {
- {{ item.label }} + {{ item.label | translate }}
} } @@ -72,7 +72,7 @@

{{ item.label }}

} @else {
- + @@ -83,7 +83,7 @@

{{ item.label }}

raised outlined severity="success" - (click)="openAddWikiDialog()" + (onClick)="openAddWikiDialog()" /> } } diff --git a/src/app/shared/components/wiki/wiki-list/wiki-list.component.scss b/src/app/shared/components/wiki/wiki-list/wiki-list.component.scss index be68f530e..49c80f9ae 100644 --- a/src/app/shared/components/wiki/wiki-list/wiki-list.component.scss +++ b/src/app/shared/components/wiki/wiki-list/wiki-list.component.scss @@ -1,9 +1,8 @@ :host { display: flex; } -.wiki-list { - width: auto; +.wiki-list { &-expanded { min-width: 300px; width: 300px; @@ -14,9 +13,3 @@ color: var(--white); } } - -@media screen and (max-width: 992px) { - .wiki-list { - width: 100%; - } -} diff --git a/src/app/shared/components/wiki/wiki-list/wiki-list.component.ts b/src/app/shared/components/wiki/wiki-list/wiki-list.component.ts index 5d3153207..3f27d32ac 100644 --- a/src/app/shared/components/wiki/wiki-list/wiki-list.component.ts +++ b/src/app/shared/components/wiki/wiki-list/wiki-list.component.ts @@ -56,7 +56,7 @@ export class WikiListComponent { { expanded: true, type: WikiItemType.Folder, - label: this.translateService.instant('project.wiki.list.header'), + label: 'project.wiki.list.header', items: this.list()?.map((wiki) => ({ id: wiki.id, label: wiki.name, @@ -68,7 +68,7 @@ export class WikiListComponent { if (this.hasComponentsWikis()) { menu.push({ type: WikiItemType.Folder, - label: this.translateService.instant('project.wiki.list.componentsHeader'), + label: 'project.wiki.list.componentsHeader', items: this.componentsList()?.map((component) => ({ id: component.id, label: component.title, @@ -88,11 +88,16 @@ export class WikiListComponent { openAddWikiDialog() { const dialogRef = this.dialogService.open(AddWikiDialogComponent, { header: this.translateService.instant('project.wiki.addNewWiki'), + focusOnShow: false, + closeOnEscape: true, modal: true, + closable: true, + width: '448px', data: { resourceId: this.resourceId(), }, }); + dialogRef.onClose.subscribe(() => { this.createWiki.emit(); }); @@ -112,8 +117,8 @@ export class WikiListComponent { private navigateTo(wikiId: string, componentId?: string) { if (componentId) { - this.router.navigateByUrl('/project').then(() => { - this.router.navigate(['/project', componentId, 'wiki'], { + this.router.navigateByUrl('/').then(() => { + this.router.navigate([componentId, 'wiki'], { queryParams: { wiki: wikiId }, }); }); diff --git a/src/app/shared/enums/resource-type.enum.ts b/src/app/shared/enums/resource-type.enum.ts index bfad8a5d6..72ef89e77 100644 --- a/src/app/shared/enums/resource-type.enum.ts +++ b/src/app/shared/enums/resource-type.enum.ts @@ -9,3 +9,11 @@ export enum ResourceType { DraftRegistration, Collection, } + +export enum CurrentResourceType { + Users = 'users', + Files = 'files', + Projects = 'nodes', + Registrations = 'registrations', + Preprints = 'preprints', +} diff --git a/src/app/shared/mappers/institutions/general-institution.mapper.ts b/src/app/shared/mappers/institutions/general-institution.mapper.ts index 065edc5b0..9a3bda1a5 100644 --- a/src/app/shared/mappers/institutions/general-institution.mapper.ts +++ b/src/app/shared/mappers/institutions/general-institution.mapper.ts @@ -13,7 +13,7 @@ export class GeneralInstitutionMapper { assets: data.attributes.assets, institutionalRequestAccessEnabled: data.attributes.institutional_request_access_enabled, logoPath: data.attributes.logo_path, - userMetricsUrl: data.relationships.user_metrics.links.related.href, + userMetricsUrl: data.relationships.user_metrics?.links.related.href, linkToExternalReportsArchive: data.attributes.link_to_external_reports_archive, }; } diff --git a/src/app/shared/models/current-resource.model.ts b/src/app/shared/models/current-resource.model.ts new file mode 100644 index 000000000..98ad6ad25 --- /dev/null +++ b/src/app/shared/models/current-resource.model.ts @@ -0,0 +1,5 @@ +export interface CurrentResource { + id: string; + type: string; + parentId?: string; +} diff --git a/src/app/shared/models/guid-response-json-api.model.ts b/src/app/shared/models/guid-response-json-api.model.ts new file mode 100644 index 000000000..438895a7b --- /dev/null +++ b/src/app/shared/models/guid-response-json-api.model.ts @@ -0,0 +1,24 @@ +import { JsonApiResponse } from './common'; + +export type GuidedResponseJsonApi = JsonApiResponse; + +interface GuidDataJsonApi { + id: string; + type: string; + attributes: { + guid: string; + }; + relationships: { + target?: { + data: IdType; + }; + provider?: { + data: IdType; + }; + }; +} + +interface IdType { + id: string; + type: string; +} diff --git a/src/app/shared/models/index.ts b/src/app/shared/models/index.ts index 9c97fdab2..74b2fb24e 100644 --- a/src/app/shared/models/index.ts +++ b/src/app/shared/models/index.ts @@ -11,10 +11,12 @@ export * from './components'; export * from './confirmation-options.model'; export * from './contributors'; export * from './create-component-form.model'; +export * from './current-resource.model'; export * from './files'; export * from './filter-labels.model'; export * from './filters'; export * from './google-drive-folder.model'; +export * from './guid-response-json-api.model'; export * from './id-name.model'; export * from './institutions'; export * from './language-code.model'; diff --git a/src/app/shared/services/index.ts b/src/app/shared/services/index.ts index 811590f08..cb1e98c72 100644 --- a/src/app/shared/services/index.ts +++ b/src/app/shared/services/index.ts @@ -17,6 +17,7 @@ export { MyResourcesService } from './my-resources.service'; export { NodeLinksService } from './node-links.service'; export { RegionsService } from './regions.service'; export { ResourceCardService } from './resource-card.service'; +export { ResourceGuidService } from './resource-guid.service'; export { SearchService } from './search.service'; export { SocialShareService } from './social-share.service'; export { SubjectsService } from './subjects.service'; diff --git a/src/app/shared/services/resource-guid.service.ts b/src/app/shared/services/resource-guid.service.ts new file mode 100644 index 000000000..ee557ad30 --- /dev/null +++ b/src/app/shared/services/resource-guid.service.ts @@ -0,0 +1,42 @@ +import { finalize, map, Observable } from 'rxjs'; + +import { inject, Injectable } from '@angular/core'; + +import { CurrentResource, GuidedResponseJsonApi } from '@osf/shared/models'; + +import { CurrentResourceType } from '../enums'; + +import { JsonApiService } from './json-api.service'; +import { LoaderService } from './loader.service'; + +import { environment } from 'src/environments/environment'; + +@Injectable({ + providedIn: 'root', +}) +export class ResourceGuidService { + private jsonApiService = inject(JsonApiService); + private loaderService = inject(LoaderService); + + getResourceById(id: string): Observable { + const baseUrl = `${environment.apiUrl}/guids/${id}/`; + + this.loaderService.show(); + + return this.jsonApiService.get(baseUrl).pipe( + map( + (res) => + ({ + id: res.data.type === CurrentResourceType.Files ? res.data.attributes.guid : res.data.id, + type: + res.data.type === CurrentResourceType.Files ? res.data.relationships.target?.data.type : res.data.type, + parentId: + res.data.type === CurrentResourceType.Preprints + ? res.data.relationships.provider?.data.id + : res.data.relationships.target?.data.id, + }) as CurrentResource + ), + finalize(() => this.loaderService.hide()) + ); + } +} diff --git a/src/app/shared/stores/current-resource/current-resource.actions.ts b/src/app/shared/stores/current-resource/current-resource.actions.ts new file mode 100644 index 000000000..ed63dc444 --- /dev/null +++ b/src/app/shared/stores/current-resource/current-resource.actions.ts @@ -0,0 +1,4 @@ +export class GetResource { + static readonly type = '[ResourceType] Get Resource Type'; + constructor(public resourceId: string) {} +} diff --git a/src/app/shared/stores/current-resource/current-resource.model.ts b/src/app/shared/stores/current-resource/current-resource.model.ts new file mode 100644 index 000000000..a299db469 --- /dev/null +++ b/src/app/shared/stores/current-resource/current-resource.model.ts @@ -0,0 +1,14 @@ +import { CurrentResource } from '@osf/shared/models'; +import { AsyncStateModel } from '@shared/models/store'; + +export interface CurrentResourceStateModel { + currentResource: AsyncStateModel; +} + +export const CURRENT_RESOURCE_DEFAULTS: CurrentResourceStateModel = { + currentResource: { + data: null, + isLoading: false, + error: null, + }, +}; diff --git a/src/app/shared/stores/current-resource/current-resource.selectors.ts b/src/app/shared/stores/current-resource/current-resource.selectors.ts new file mode 100644 index 000000000..5b321c247 --- /dev/null +++ b/src/app/shared/stores/current-resource/current-resource.selectors.ts @@ -0,0 +1,13 @@ +import { Selector } from '@ngxs/store'; + +import { CurrentResource } from '@osf/shared/models'; + +import { CurrentResourceStateModel } from './current-resource.model'; +import { CurrentResourceState } from './current-resource.state'; + +export class CurrentResourceSelectors { + @Selector([CurrentResourceState]) + static getCurrentResource(state: CurrentResourceStateModel): CurrentResource | null { + return state.currentResource.data; + } +} diff --git a/src/app/shared/stores/current-resource/current-resource.state.ts b/src/app/shared/stores/current-resource/current-resource.state.ts new file mode 100644 index 000000000..7114353ef --- /dev/null +++ b/src/app/shared/stores/current-resource/current-resource.state.ts @@ -0,0 +1,50 @@ +import { Action, State, StateContext } from '@ngxs/store'; + +import { catchError, tap } from 'rxjs'; + +import { inject, Injectable } from '@angular/core'; + +import { handleSectionError } from '@osf/shared/helpers'; +import { ResourceGuidService } from '@osf/shared/services'; + +import { GetResource } from './current-resource.actions'; +import { CURRENT_RESOURCE_DEFAULTS, CurrentResourceStateModel } from './current-resource.model'; + +@State({ + name: 'currentResource', + defaults: CURRENT_RESOURCE_DEFAULTS, +}) +@Injectable() +export class CurrentResourceState { + private resourceTypeService = inject(ResourceGuidService); + + @Action(GetResource) + getResourceType(ctx: StateContext, action: GetResource) { + const state = ctx.getState(); + + if (state.currentResource.data?.id === action.resourceId) { + return; + } + + ctx.patchState({ + currentResource: { + ...state.currentResource, + isLoading: true, + error: null, + }, + }); + + return this.resourceTypeService.getResourceById(action.resourceId).pipe( + tap((resourceType) => { + ctx.patchState({ + currentResource: { + data: resourceType, + isLoading: false, + error: null, + }, + }); + }), + catchError((error) => handleSectionError(ctx, 'currentResource', error)) + ); + } +} diff --git a/src/app/shared/stores/current-resource/index.ts b/src/app/shared/stores/current-resource/index.ts new file mode 100644 index 000000000..2efdd7633 --- /dev/null +++ b/src/app/shared/stores/current-resource/index.ts @@ -0,0 +1,4 @@ +export * from './current-resource.actions'; +export * from './current-resource.model'; +export * from './current-resource.selectors'; +export * from './current-resource.state'; diff --git a/src/app/shared/stores/index.ts b/src/app/shared/stores/index.ts index 66f706c5b..88be28355 100644 --- a/src/app/shared/stores/index.ts +++ b/src/app/shared/stores/index.ts @@ -3,6 +3,7 @@ export * from './bookmarks'; export * from './citations'; export * from './collections'; export * from './contributors'; +export * from './current-resource'; export * from './duplicates'; export * from './institutions'; export * from './institutions-search';