From f2d420bdc5d2d7955a195e80aa47ad862962df86 Mon Sep 17 00:00:00 2001 From: nsemets Date: Tue, 5 Aug 2025 19:40:30 +0300 Subject: [PATCH 1/6] feat(guid-routing): added guid routing --- src/app/app.routes.ts | 23 +++--- .../nav-menu/nav-menu.component.html | 4 +- src/app/features/registry/registry.routes.ts | 4 +- src/app/shared/guards/index.ts | 2 + src/app/shared/guards/is-project.guard.ts | 39 ++++++++++ src/app/shared/guards/is-registry.guard.ts | 39 ++++++++++ .../models/guid-response-json-api.model.ts | 7 ++ src/app/shared/models/index.ts | 1 + .../shared/services/resource-type.service.ts | 31 ++++++++ src/app/shared/stores/index.ts | 1 + src/app/shared/stores/resource-type/index.ts | 4 + .../resource-type/resource-type.actions.ts | 18 +++++ .../resource-type/resource-type.model.ts | 16 ++++ .../resource-type/resource-type.selectors.ts | 28 +++++++ .../resource-type/resource-type.state.ts | 76 +++++++++++++++++++ 15 files changed, 280 insertions(+), 13 deletions(-) create mode 100644 src/app/shared/guards/is-project.guard.ts create mode 100644 src/app/shared/guards/is-registry.guard.ts create mode 100644 src/app/shared/models/guid-response-json-api.model.ts create mode 100644 src/app/shared/services/resource-type.service.ts create mode 100644 src/app/shared/stores/resource-type/index.ts create mode 100644 src/app/shared/stores/resource-type/resource-type.actions.ts create mode 100644 src/app/shared/stores/resource-type/resource-type.model.ts create mode 100644 src/app/shared/stores/resource-type/resource-type.selectors.ts create mode 100644 src/app/shared/stores/resource-type/resource-type.state.ts diff --git a/src/app/app.routes.ts b/src/app/app.routes.ts index 6cf60eb28..ccd669917 100644 --- a/src/app/app.routes.ts +++ b/src/app/app.routes.ts @@ -2,7 +2,7 @@ import { provideStates } from '@ngxs/store'; import { Routes } from '@angular/router'; -import { BookmarksState, ProjectsState } from '@shared/stores'; +import { BookmarksState, ProjectsState, ResourceTypeState } from '@shared/stores'; import { MyProfileResourceFiltersOptionsState } from './features/my-profile/components/filters/store'; import { MyProfileResourceFiltersState } from './features/my-profile/components/my-profile-resource-filters/store'; @@ -10,6 +10,8 @@ import { MyProfileState } from './features/my-profile/store'; import { ResourceFiltersOptionsState } from './features/search/components/filters/store'; import { ResourceFiltersState } from './features/search/components/resource-filters/store'; import { SearchState } from './features/search/store'; +import { isProjectGuard } from './shared/guards/is-project.guard'; +import { isRegistryGuard } from './shared/guards/is-registry.guard'; export const routes: Routes = [ { @@ -76,9 +78,16 @@ export const routes: Routes = [ providers: [provideStates([BookmarksState])], }, { - path: 'my-projects/:id', - loadChildren: () => import('./features/project/project.routes').then((mod) => mod.projectRoutes), - providers: [provideStates([ProjectsState, BookmarksState])], + path: ':id', + canMatch: [isProjectGuard], + loadChildren: () => import('./features/project/project.routes').then((m) => m.projectRoutes), + providers: [provideStates([ProjectsState, BookmarksState, ResourceTypeState])], + }, + { + path: ':id', + canMatch: [isRegistryGuard], + loadChildren: () => import('./features/registry/registry.routes').then((m) => m.registryRoutes), + providers: [provideStates([BookmarksState, ResourceTypeState])], }, { path: 'settings', @@ -108,11 +117,7 @@ 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])], - }, + { path: 'terms-of-use', loadComponent: () => diff --git a/src/app/core/components/nav-menu/nav-menu.component.html b/src/app/core/components/nav-menu/nav-menu.component.html index 6a1540ea4..08361ab21 100644 --- a/src/app/core/components/nav-menu/nav-menu.component.html +++ b/src/app/core/components/nav-menu/nav-menu.component.html @@ -28,7 +28,7 @@ import('./pages/registry-overview/registry-overview.component').then((c) => c.RegistryOverviewComponent), + providers: [provideStates([RegistryOverviewState, CitationsState])], }, { path: 'metadata', diff --git a/src/app/shared/guards/index.ts b/src/app/shared/guards/index.ts index c0ba5d4a2..dc1d8b9af 100644 --- a/src/app/shared/guards/index.ts +++ b/src/app/shared/guards/index.ts @@ -1 +1,3 @@ export { ConfirmLeavingGuard } from './confirm-leaving.guard'; +export { isProjectGuard } from './is-project.guard'; +export { isRegistryGuard } from './is-registry.guard'; diff --git a/src/app/shared/guards/is-project.guard.ts b/src/app/shared/guards/is-project.guard.ts new file mode 100644 index 000000000..3a8cbd8f0 --- /dev/null +++ b/src/app/shared/guards/is-project.guard.ts @@ -0,0 +1,39 @@ +import { Store } from '@ngxs/store'; + +import { lastValueFrom } from 'rxjs'; + +import { inject } from '@angular/core'; +import { CanMatchFn, Route, UrlSegment } from '@angular/router'; + +import { ResourceType } from '../enums'; +import { ResourceTypeService } from '../services/resource-type.service'; +import { ResourceTypeSelectors, SetResourceType } from '../stores'; + +export const isProjectGuard: CanMatchFn = async (route: Route, segments: UrlSegment[]) => { + const resourceTypeService = inject(ResourceTypeService); + const store = inject(Store); + + const id = segments[0]?.path; + + if (!id) { + return false; + } + + const currentResourceId = store.selectSnapshot(ResourceTypeSelectors.getCurrentResourceId); + const currentResourceType = store.selectSnapshot(ResourceTypeSelectors.getCurrentResourceType); + + if (currentResourceId === id && currentResourceType !== null) { + return currentResourceType === ResourceType.Project; + } + + try { + const resourceType = await lastValueFrom(resourceTypeService.getResourceType(id)); + const isProject = resourceType === ResourceType.Project; + + store.dispatch(new SetResourceType(id, resourceType)); + + return isProject; + } catch { + return false; + } +}; diff --git a/src/app/shared/guards/is-registry.guard.ts b/src/app/shared/guards/is-registry.guard.ts new file mode 100644 index 000000000..b9d0004a7 --- /dev/null +++ b/src/app/shared/guards/is-registry.guard.ts @@ -0,0 +1,39 @@ +import { Store } from '@ngxs/store'; + +import { lastValueFrom } from 'rxjs'; + +import { inject } from '@angular/core'; +import { CanMatchFn, Route, UrlSegment } from '@angular/router'; + +import { ResourceType } from '../enums'; +import { ResourceTypeService } from '../services/resource-type.service'; +import { ResourceTypeSelectors, SetResourceType } from '../stores'; + +export const isRegistryGuard: CanMatchFn = async (route: Route, segments: UrlSegment[]) => { + const resourceTypeService = inject(ResourceTypeService); + const store = inject(Store); + + const id = segments[0]?.path; + + if (!id) { + return false; + } + + const currentResourceId = store.selectSnapshot(ResourceTypeSelectors.getCurrentResourceId); + const currentResourceType = store.selectSnapshot(ResourceTypeSelectors.getCurrentResourceType); + + if (currentResourceId === id && currentResourceType !== null) { + return currentResourceType === ResourceType.Registration; + } + + try { + const resourceType = await lastValueFrom(resourceTypeService.getResourceType(id)); + const isRegistry = resourceType === ResourceType.Registration; + + store.dispatch(new SetResourceType(id, resourceType)); + + return isRegistry; + } catch { + return false; + } +}; 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..534acd20f --- /dev/null +++ b/src/app/shared/models/guid-response-json-api.model.ts @@ -0,0 +1,7 @@ +import { JsonApiResponse } from '@osf/core/models'; + +export type GuidedResponseJsonApi = JsonApiResponse; + +interface GuidDataJsonApi { + type: string; +} diff --git a/src/app/shared/models/index.ts b/src/app/shared/models/index.ts index 891d37ecc..28d88c49a 100644 --- a/src/app/shared/models/index.ts +++ b/src/app/shared/models/index.ts @@ -13,6 +13,7 @@ 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/resource-type.service.ts b/src/app/shared/services/resource-type.service.ts new file mode 100644 index 000000000..96465e271 --- /dev/null +++ b/src/app/shared/services/resource-type.service.ts @@ -0,0 +1,31 @@ +import { finalize, map, Observable } from 'rxjs'; + +import { inject, Injectable } from '@angular/core'; + +import { JsonApiService } from '@core/services'; + +import { ResourceType } from '../enums'; +import { GuidedResponseJsonApi } from '../models'; + +import { LoaderService } from './loader.service'; + +import { environment } from 'src/environments/environment'; + +@Injectable({ + providedIn: 'root', +}) +export class ResourceTypeService { + private jsonApiService = inject(JsonApiService); + private loaderService = inject(LoaderService); + + getResourceType(id: string): Observable { + const baseUrl = `${environment.apiUrl}/guids/${id}/`; + + this.loaderService.show(); + + return this.jsonApiService.get(baseUrl).pipe( + map((res) => (res.data.type === 'nodes' ? ResourceType.Project : ResourceType.Registration)), + finalize(() => this.loaderService.hide()) + ); + } +} diff --git a/src/app/shared/stores/index.ts b/src/app/shared/stores/index.ts index 2350f9cb5..c42d93f16 100644 --- a/src/app/shared/stores/index.ts +++ b/src/app/shared/stores/index.ts @@ -9,6 +9,7 @@ export * from './licenses'; export * from './my-resources'; export * from './node-links'; export * from './projects'; +export * from './resource-type'; export * from './subjects'; export * from './view-only-links'; export * from './wiki'; diff --git a/src/app/shared/stores/resource-type/index.ts b/src/app/shared/stores/resource-type/index.ts new file mode 100644 index 000000000..41a46a9d3 --- /dev/null +++ b/src/app/shared/stores/resource-type/index.ts @@ -0,0 +1,4 @@ +export * from './resource-type.actions'; +export * from './resource-type.model'; +export * from './resource-type.selectors'; +export * from './resource-type.state'; diff --git a/src/app/shared/stores/resource-type/resource-type.actions.ts b/src/app/shared/stores/resource-type/resource-type.actions.ts new file mode 100644 index 000000000..5e22ef0e3 --- /dev/null +++ b/src/app/shared/stores/resource-type/resource-type.actions.ts @@ -0,0 +1,18 @@ +import { ResourceType } from '@shared/enums'; + +export class SetResourceType { + static readonly type = '[ResourceType] Set Resource Type'; + constructor( + public resourceId: string, + public resourceType: ResourceType + ) {} +} + +export class GetResourceType { + static readonly type = '[ResourceType] Get Resource Type'; + constructor(public resourceId: string) {} +} + +export class ClearResourceType { + static readonly type = '[ResourceType] Clear Resource Type'; +} diff --git a/src/app/shared/stores/resource-type/resource-type.model.ts b/src/app/shared/stores/resource-type/resource-type.model.ts new file mode 100644 index 000000000..0edca7095 --- /dev/null +++ b/src/app/shared/stores/resource-type/resource-type.model.ts @@ -0,0 +1,16 @@ +import { ResourceType } from '@shared/enums'; +import { AsyncStateModel } from '@shared/models/store'; + +export interface ResourceTypeStateModel { + currentResourceType: AsyncStateModel; + currentResourceId: string | null; +} + +export const RESOURCE_TYPE_DEFAULTS: ResourceTypeStateModel = { + currentResourceType: { + data: null, + isLoading: false, + error: null, + }, + currentResourceId: null, +}; diff --git a/src/app/shared/stores/resource-type/resource-type.selectors.ts b/src/app/shared/stores/resource-type/resource-type.selectors.ts new file mode 100644 index 000000000..660ba2664 --- /dev/null +++ b/src/app/shared/stores/resource-type/resource-type.selectors.ts @@ -0,0 +1,28 @@ +import { Selector } from '@ngxs/store'; + +import { ResourceType } from '@shared/enums'; + +import { ResourceTypeStateModel } from './resource-type.model'; +import { ResourceTypeState } from './resource-type.state'; + +export class ResourceTypeSelectors { + @Selector([ResourceTypeState]) + static getCurrentResourceType(state: ResourceTypeStateModel): ResourceType | null { + return state.currentResourceType.data; + } + + @Selector([ResourceTypeState]) + static getCurrentResourceId(state: ResourceTypeStateModel): string | null { + return state.currentResourceId; + } + + @Selector([ResourceTypeState]) + static isCurrentResourceProject(state: ResourceTypeStateModel): boolean { + return state.currentResourceType.data === ResourceType.Project; + } + + @Selector([ResourceTypeState]) + static isCurrentResourceRegistry(state: ResourceTypeStateModel): boolean { + return state.currentResourceType.data === ResourceType.Registration; + } +} diff --git a/src/app/shared/stores/resource-type/resource-type.state.ts b/src/app/shared/stores/resource-type/resource-type.state.ts new file mode 100644 index 000000000..c963192d0 --- /dev/null +++ b/src/app/shared/stores/resource-type/resource-type.state.ts @@ -0,0 +1,76 @@ +import { Action, State, StateContext } from '@ngxs/store'; + +import { catchError, of, tap } from 'rxjs'; + +import { inject, Injectable } from '@angular/core'; + +import { ResourceTypeService } from '@osf/shared/services/resource-type.service'; + +import { ClearResourceType, GetResourceType, SetResourceType } from './resource-type.actions'; +import { RESOURCE_TYPE_DEFAULTS, ResourceTypeStateModel } from './resource-type.model'; + +@State({ + name: 'resourceType', + defaults: RESOURCE_TYPE_DEFAULTS, +}) +@Injectable() +export class ResourceTypeState { + private resourceTypeService = inject(ResourceTypeService); + + @Action(SetResourceType) + setResourceType(ctx: StateContext, action: SetResourceType) { + ctx.patchState({ + currentResourceType: { + data: action.resourceType, + isLoading: false, + error: null, + }, + currentResourceId: action.resourceId, + }); + } + + @Action(GetResourceType) + getResourceType(ctx: StateContext, action: GetResourceType) { + const state = ctx.getState(); + + if (state.currentResourceId === action.resourceId && state.currentResourceType.data) { + return; + } + + ctx.patchState({ + currentResourceType: { + ...state.currentResourceType, + isLoading: true, + error: null, + }, + currentResourceId: action.resourceId, + }); + + return this.resourceTypeService.getResourceType(action.resourceId).pipe( + tap((resourceType) => { + ctx.patchState({ + currentResourceType: { + data: resourceType, + isLoading: false, + error: null, + }, + }); + }), + catchError((error) => { + ctx.patchState({ + currentResourceType: { + data: null, + isLoading: false, + error, + }, + }); + return of(null); + }) + ); + } + + @Action(ClearResourceType) + clearResourceType(ctx: StateContext) { + ctx.setState(RESOURCE_TYPE_DEFAULTS); + } +} From f43ffa7558d1b7c30ba02a3591494178787fe43c Mon Sep 17 00:00:00 2001 From: nsemets Date: Tue, 19 Aug 2025 15:51:01 +0300 Subject: [PATCH 2/6] feat(guid-routing): added guid routing --- src/app/app.routes.ts | 23 +++--- .../nav-menu/nav-menu.component.html | 4 +- src/app/features/registry/registry.routes.ts | 4 +- src/app/shared/guards/index.ts | 2 + src/app/shared/guards/is-project.guard.ts | 39 ++++++++++ src/app/shared/guards/is-registry.guard.ts | 39 ++++++++++ .../models/guid-response-json-api.model.ts | 7 ++ src/app/shared/models/index.ts | 1 + .../shared/services/resource-type.service.ts | 31 ++++++++ src/app/shared/stores/index.ts | 1 + src/app/shared/stores/resource-type/index.ts | 4 + .../resource-type/resource-type.actions.ts | 18 +++++ .../resource-type/resource-type.model.ts | 16 ++++ .../resource-type/resource-type.selectors.ts | 28 +++++++ .../resource-type/resource-type.state.ts | 76 +++++++++++++++++++ 15 files changed, 280 insertions(+), 13 deletions(-) create mode 100644 src/app/shared/guards/is-project.guard.ts create mode 100644 src/app/shared/guards/is-registry.guard.ts create mode 100644 src/app/shared/models/guid-response-json-api.model.ts create mode 100644 src/app/shared/services/resource-type.service.ts create mode 100644 src/app/shared/stores/resource-type/index.ts create mode 100644 src/app/shared/stores/resource-type/resource-type.actions.ts create mode 100644 src/app/shared/stores/resource-type/resource-type.model.ts create mode 100644 src/app/shared/stores/resource-type/resource-type.selectors.ts create mode 100644 src/app/shared/stores/resource-type/resource-type.state.ts diff --git a/src/app/app.routes.ts b/src/app/app.routes.ts index 6cf60eb28..ccd669917 100644 --- a/src/app/app.routes.ts +++ b/src/app/app.routes.ts @@ -2,7 +2,7 @@ import { provideStates } from '@ngxs/store'; import { Routes } from '@angular/router'; -import { BookmarksState, ProjectsState } from '@shared/stores'; +import { BookmarksState, ProjectsState, ResourceTypeState } from '@shared/stores'; import { MyProfileResourceFiltersOptionsState } from './features/my-profile/components/filters/store'; import { MyProfileResourceFiltersState } from './features/my-profile/components/my-profile-resource-filters/store'; @@ -10,6 +10,8 @@ import { MyProfileState } from './features/my-profile/store'; import { ResourceFiltersOptionsState } from './features/search/components/filters/store'; import { ResourceFiltersState } from './features/search/components/resource-filters/store'; import { SearchState } from './features/search/store'; +import { isProjectGuard } from './shared/guards/is-project.guard'; +import { isRegistryGuard } from './shared/guards/is-registry.guard'; export const routes: Routes = [ { @@ -76,9 +78,16 @@ export const routes: Routes = [ providers: [provideStates([BookmarksState])], }, { - path: 'my-projects/:id', - loadChildren: () => import('./features/project/project.routes').then((mod) => mod.projectRoutes), - providers: [provideStates([ProjectsState, BookmarksState])], + path: ':id', + canMatch: [isProjectGuard], + loadChildren: () => import('./features/project/project.routes').then((m) => m.projectRoutes), + providers: [provideStates([ProjectsState, BookmarksState, ResourceTypeState])], + }, + { + path: ':id', + canMatch: [isRegistryGuard], + loadChildren: () => import('./features/registry/registry.routes').then((m) => m.registryRoutes), + providers: [provideStates([BookmarksState, ResourceTypeState])], }, { path: 'settings', @@ -108,11 +117,7 @@ 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])], - }, + { path: 'terms-of-use', loadComponent: () => diff --git a/src/app/core/components/nav-menu/nav-menu.component.html b/src/app/core/components/nav-menu/nav-menu.component.html index 6a1540ea4..08361ab21 100644 --- a/src/app/core/components/nav-menu/nav-menu.component.html +++ b/src/app/core/components/nav-menu/nav-menu.component.html @@ -28,7 +28,7 @@ import('./pages/registry-overview/registry-overview.component').then((c) => c.RegistryOverviewComponent), + providers: [provideStates([RegistryOverviewState, CitationsState])], }, { path: 'metadata', diff --git a/src/app/shared/guards/index.ts b/src/app/shared/guards/index.ts index c0ba5d4a2..dc1d8b9af 100644 --- a/src/app/shared/guards/index.ts +++ b/src/app/shared/guards/index.ts @@ -1 +1,3 @@ export { ConfirmLeavingGuard } from './confirm-leaving.guard'; +export { isProjectGuard } from './is-project.guard'; +export { isRegistryGuard } from './is-registry.guard'; diff --git a/src/app/shared/guards/is-project.guard.ts b/src/app/shared/guards/is-project.guard.ts new file mode 100644 index 000000000..3a8cbd8f0 --- /dev/null +++ b/src/app/shared/guards/is-project.guard.ts @@ -0,0 +1,39 @@ +import { Store } from '@ngxs/store'; + +import { lastValueFrom } from 'rxjs'; + +import { inject } from '@angular/core'; +import { CanMatchFn, Route, UrlSegment } from '@angular/router'; + +import { ResourceType } from '../enums'; +import { ResourceTypeService } from '../services/resource-type.service'; +import { ResourceTypeSelectors, SetResourceType } from '../stores'; + +export const isProjectGuard: CanMatchFn = async (route: Route, segments: UrlSegment[]) => { + const resourceTypeService = inject(ResourceTypeService); + const store = inject(Store); + + const id = segments[0]?.path; + + if (!id) { + return false; + } + + const currentResourceId = store.selectSnapshot(ResourceTypeSelectors.getCurrentResourceId); + const currentResourceType = store.selectSnapshot(ResourceTypeSelectors.getCurrentResourceType); + + if (currentResourceId === id && currentResourceType !== null) { + return currentResourceType === ResourceType.Project; + } + + try { + const resourceType = await lastValueFrom(resourceTypeService.getResourceType(id)); + const isProject = resourceType === ResourceType.Project; + + store.dispatch(new SetResourceType(id, resourceType)); + + return isProject; + } catch { + return false; + } +}; diff --git a/src/app/shared/guards/is-registry.guard.ts b/src/app/shared/guards/is-registry.guard.ts new file mode 100644 index 000000000..b9d0004a7 --- /dev/null +++ b/src/app/shared/guards/is-registry.guard.ts @@ -0,0 +1,39 @@ +import { Store } from '@ngxs/store'; + +import { lastValueFrom } from 'rxjs'; + +import { inject } from '@angular/core'; +import { CanMatchFn, Route, UrlSegment } from '@angular/router'; + +import { ResourceType } from '../enums'; +import { ResourceTypeService } from '../services/resource-type.service'; +import { ResourceTypeSelectors, SetResourceType } from '../stores'; + +export const isRegistryGuard: CanMatchFn = async (route: Route, segments: UrlSegment[]) => { + const resourceTypeService = inject(ResourceTypeService); + const store = inject(Store); + + const id = segments[0]?.path; + + if (!id) { + return false; + } + + const currentResourceId = store.selectSnapshot(ResourceTypeSelectors.getCurrentResourceId); + const currentResourceType = store.selectSnapshot(ResourceTypeSelectors.getCurrentResourceType); + + if (currentResourceId === id && currentResourceType !== null) { + return currentResourceType === ResourceType.Registration; + } + + try { + const resourceType = await lastValueFrom(resourceTypeService.getResourceType(id)); + const isRegistry = resourceType === ResourceType.Registration; + + store.dispatch(new SetResourceType(id, resourceType)); + + return isRegistry; + } catch { + return false; + } +}; 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..534acd20f --- /dev/null +++ b/src/app/shared/models/guid-response-json-api.model.ts @@ -0,0 +1,7 @@ +import { JsonApiResponse } from '@osf/core/models'; + +export type GuidedResponseJsonApi = JsonApiResponse; + +interface GuidDataJsonApi { + type: string; +} diff --git a/src/app/shared/models/index.ts b/src/app/shared/models/index.ts index 891d37ecc..28d88c49a 100644 --- a/src/app/shared/models/index.ts +++ b/src/app/shared/models/index.ts @@ -13,6 +13,7 @@ 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/resource-type.service.ts b/src/app/shared/services/resource-type.service.ts new file mode 100644 index 000000000..96465e271 --- /dev/null +++ b/src/app/shared/services/resource-type.service.ts @@ -0,0 +1,31 @@ +import { finalize, map, Observable } from 'rxjs'; + +import { inject, Injectable } from '@angular/core'; + +import { JsonApiService } from '@core/services'; + +import { ResourceType } from '../enums'; +import { GuidedResponseJsonApi } from '../models'; + +import { LoaderService } from './loader.service'; + +import { environment } from 'src/environments/environment'; + +@Injectable({ + providedIn: 'root', +}) +export class ResourceTypeService { + private jsonApiService = inject(JsonApiService); + private loaderService = inject(LoaderService); + + getResourceType(id: string): Observable { + const baseUrl = `${environment.apiUrl}/guids/${id}/`; + + this.loaderService.show(); + + return this.jsonApiService.get(baseUrl).pipe( + map((res) => (res.data.type === 'nodes' ? ResourceType.Project : ResourceType.Registration)), + finalize(() => this.loaderService.hide()) + ); + } +} diff --git a/src/app/shared/stores/index.ts b/src/app/shared/stores/index.ts index 2350f9cb5..c42d93f16 100644 --- a/src/app/shared/stores/index.ts +++ b/src/app/shared/stores/index.ts @@ -9,6 +9,7 @@ export * from './licenses'; export * from './my-resources'; export * from './node-links'; export * from './projects'; +export * from './resource-type'; export * from './subjects'; export * from './view-only-links'; export * from './wiki'; diff --git a/src/app/shared/stores/resource-type/index.ts b/src/app/shared/stores/resource-type/index.ts new file mode 100644 index 000000000..41a46a9d3 --- /dev/null +++ b/src/app/shared/stores/resource-type/index.ts @@ -0,0 +1,4 @@ +export * from './resource-type.actions'; +export * from './resource-type.model'; +export * from './resource-type.selectors'; +export * from './resource-type.state'; diff --git a/src/app/shared/stores/resource-type/resource-type.actions.ts b/src/app/shared/stores/resource-type/resource-type.actions.ts new file mode 100644 index 000000000..5e22ef0e3 --- /dev/null +++ b/src/app/shared/stores/resource-type/resource-type.actions.ts @@ -0,0 +1,18 @@ +import { ResourceType } from '@shared/enums'; + +export class SetResourceType { + static readonly type = '[ResourceType] Set Resource Type'; + constructor( + public resourceId: string, + public resourceType: ResourceType + ) {} +} + +export class GetResourceType { + static readonly type = '[ResourceType] Get Resource Type'; + constructor(public resourceId: string) {} +} + +export class ClearResourceType { + static readonly type = '[ResourceType] Clear Resource Type'; +} diff --git a/src/app/shared/stores/resource-type/resource-type.model.ts b/src/app/shared/stores/resource-type/resource-type.model.ts new file mode 100644 index 000000000..0edca7095 --- /dev/null +++ b/src/app/shared/stores/resource-type/resource-type.model.ts @@ -0,0 +1,16 @@ +import { ResourceType } from '@shared/enums'; +import { AsyncStateModel } from '@shared/models/store'; + +export interface ResourceTypeStateModel { + currentResourceType: AsyncStateModel; + currentResourceId: string | null; +} + +export const RESOURCE_TYPE_DEFAULTS: ResourceTypeStateModel = { + currentResourceType: { + data: null, + isLoading: false, + error: null, + }, + currentResourceId: null, +}; diff --git a/src/app/shared/stores/resource-type/resource-type.selectors.ts b/src/app/shared/stores/resource-type/resource-type.selectors.ts new file mode 100644 index 000000000..660ba2664 --- /dev/null +++ b/src/app/shared/stores/resource-type/resource-type.selectors.ts @@ -0,0 +1,28 @@ +import { Selector } from '@ngxs/store'; + +import { ResourceType } from '@shared/enums'; + +import { ResourceTypeStateModel } from './resource-type.model'; +import { ResourceTypeState } from './resource-type.state'; + +export class ResourceTypeSelectors { + @Selector([ResourceTypeState]) + static getCurrentResourceType(state: ResourceTypeStateModel): ResourceType | null { + return state.currentResourceType.data; + } + + @Selector([ResourceTypeState]) + static getCurrentResourceId(state: ResourceTypeStateModel): string | null { + return state.currentResourceId; + } + + @Selector([ResourceTypeState]) + static isCurrentResourceProject(state: ResourceTypeStateModel): boolean { + return state.currentResourceType.data === ResourceType.Project; + } + + @Selector([ResourceTypeState]) + static isCurrentResourceRegistry(state: ResourceTypeStateModel): boolean { + return state.currentResourceType.data === ResourceType.Registration; + } +} diff --git a/src/app/shared/stores/resource-type/resource-type.state.ts b/src/app/shared/stores/resource-type/resource-type.state.ts new file mode 100644 index 000000000..c963192d0 --- /dev/null +++ b/src/app/shared/stores/resource-type/resource-type.state.ts @@ -0,0 +1,76 @@ +import { Action, State, StateContext } from '@ngxs/store'; + +import { catchError, of, tap } from 'rxjs'; + +import { inject, Injectable } from '@angular/core'; + +import { ResourceTypeService } from '@osf/shared/services/resource-type.service'; + +import { ClearResourceType, GetResourceType, SetResourceType } from './resource-type.actions'; +import { RESOURCE_TYPE_DEFAULTS, ResourceTypeStateModel } from './resource-type.model'; + +@State({ + name: 'resourceType', + defaults: RESOURCE_TYPE_DEFAULTS, +}) +@Injectable() +export class ResourceTypeState { + private resourceTypeService = inject(ResourceTypeService); + + @Action(SetResourceType) + setResourceType(ctx: StateContext, action: SetResourceType) { + ctx.patchState({ + currentResourceType: { + data: action.resourceType, + isLoading: false, + error: null, + }, + currentResourceId: action.resourceId, + }); + } + + @Action(GetResourceType) + getResourceType(ctx: StateContext, action: GetResourceType) { + const state = ctx.getState(); + + if (state.currentResourceId === action.resourceId && state.currentResourceType.data) { + return; + } + + ctx.patchState({ + currentResourceType: { + ...state.currentResourceType, + isLoading: true, + error: null, + }, + currentResourceId: action.resourceId, + }); + + return this.resourceTypeService.getResourceType(action.resourceId).pipe( + tap((resourceType) => { + ctx.patchState({ + currentResourceType: { + data: resourceType, + isLoading: false, + error: null, + }, + }); + }), + catchError((error) => { + ctx.patchState({ + currentResourceType: { + data: null, + isLoading: false, + error, + }, + }); + return of(null); + }) + ); + } + + @Action(ClearResourceType) + clearResourceType(ctx: StateContext) { + ctx.setState(RESOURCE_TYPE_DEFAULTS); + } +} From 4ab3bf2bed79e917f8f88876f7857ce856275a66 Mon Sep 17 00:00:00 2001 From: nsemets Date: Tue, 19 Aug 2025 17:50:27 +0300 Subject: [PATCH 3/6] fix(states): moved some states --- src/app/core/constants/ngxs-states.constant.ts | 8 -------- src/app/features/meetings/meetings.routes.ts | 4 ++++ .../settings/developer-apps/developer-apps.route.ts | 4 ++++ src/app/features/settings/settings.routes.ts | 6 ++++++ 4 files changed, 14 insertions(+), 8 deletions(-) diff --git a/src/app/core/constants/ngxs-states.constant.ts b/src/app/core/constants/ngxs-states.constant.ts index b6dd46146..df71c854d 100644 --- a/src/app/core/constants/ngxs-states.constant.ts +++ b/src/app/core/constants/ngxs-states.constant.ts @@ -1,12 +1,8 @@ import { ProviderState } from '@core/store/provider'; import { UserState } from '@core/store/user'; -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 { LicensesState } from '@shared/stores/licenses'; import { MyResourcesState } from '@shared/stores/my-resources'; @@ -18,12 +14,8 @@ export const STATES = [ ProviderState, MyResourcesState, InstitutionsState, - DeveloperAppsState, - AccountSettingsState, - NotificationSubscriptionState, ProjectOverviewState, WikiState, - MeetingsState, RegistrationsState, ProjectMetadataState, LicensesState, 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/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])], }, ], }, From 535c67dc9360e543e16242e6f7c57a7afff6cc1e Mon Sep 17 00:00:00 2001 From: nsemets Date: Wed, 20 Aug 2025 15:53:21 +0300 Subject: [PATCH 4/6] feat(guid-routing): updated current resource state and service --- src/app/shared/enums/resource-type.enum.ts | 8 ++ .../shared/models/current-resource.model.ts | 4 + .../models/guid-response-json-api.model.ts | 3 +- src/app/shared/models/index.ts | 1 + src/app/shared/services/index.ts | 1 + ...pe.service.ts => resource-guid.service.ts} | 20 +++-- .../current-resource.actions.ts} | 17 ++--- .../current-resource.model.ts | 14 ++++ .../current-resource.selectors.ts | 24 ++++++ .../current-resource.state.ts | 66 ++++++++++++++++ .../shared/stores/current-resource/index.ts | 4 + src/app/shared/stores/index.ts | 2 +- src/app/shared/stores/resource-type/index.ts | 4 - .../resource-type/resource-type.model.ts | 16 ---- .../resource-type/resource-type.selectors.ts | 28 ------- .../resource-type/resource-type.state.ts | 76 ------------------- 16 files changed, 144 insertions(+), 144 deletions(-) create mode 100644 src/app/shared/models/current-resource.model.ts rename src/app/shared/services/{resource-type.service.ts => resource-guid.service.ts} (54%) rename src/app/shared/stores/{resource-type/resource-type.actions.ts => current-resource/current-resource.actions.ts} (57%) create mode 100644 src/app/shared/stores/current-resource/current-resource.model.ts create mode 100644 src/app/shared/stores/current-resource/current-resource.selectors.ts create mode 100644 src/app/shared/stores/current-resource/current-resource.state.ts create mode 100644 src/app/shared/stores/current-resource/index.ts delete mode 100644 src/app/shared/stores/resource-type/index.ts delete mode 100644 src/app/shared/stores/resource-type/resource-type.model.ts delete mode 100644 src/app/shared/stores/resource-type/resource-type.selectors.ts delete mode 100644 src/app/shared/stores/resource-type/resource-type.state.ts diff --git a/src/app/shared/enums/resource-type.enum.ts b/src/app/shared/enums/resource-type.enum.ts index bfad8a5d6..0f4094070 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 { + User = 'users', + File = 'files', + Project = 'nodes', + Registration = 'registrations', + Preprint = 'preprints', +} 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..452e30143 --- /dev/null +++ b/src/app/shared/models/current-resource.model.ts @@ -0,0 +1,4 @@ +export interface CurrentResource { + id: string; + type: 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 index 534acd20f..60101769f 100644 --- a/src/app/shared/models/guid-response-json-api.model.ts +++ b/src/app/shared/models/guid-response-json-api.model.ts @@ -1,7 +1,8 @@ -import { JsonApiResponse } from '@osf/core/models'; +import { JsonApiResponse } from './common'; export type GuidedResponseJsonApi = JsonApiResponse; interface GuidDataJsonApi { + id: string; type: string; } diff --git a/src/app/shared/models/index.ts b/src/app/shared/models/index.ts index 17bd30162..74b2fb24e 100644 --- a/src/app/shared/models/index.ts +++ b/src/app/shared/models/index.ts @@ -11,6 +11,7 @@ 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'; 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-type.service.ts b/src/app/shared/services/resource-guid.service.ts similarity index 54% rename from src/app/shared/services/resource-type.service.ts rename to src/app/shared/services/resource-guid.service.ts index 96465e271..dc1d86e4f 100644 --- a/src/app/shared/services/resource-type.service.ts +++ b/src/app/shared/services/resource-guid.service.ts @@ -2,29 +2,33 @@ import { finalize, map, Observable } from 'rxjs'; import { inject, Injectable } from '@angular/core'; -import { JsonApiService } from '@core/services'; +import { CurrentResource, GuidedResponseJsonApi } from '@osf/shared/models'; -import { ResourceType } from '../enums'; -import { GuidedResponseJsonApi } from '../models'; +import { environment } from '../../../environments/environment'; +import { JsonApiService } from './json-api.service'; import { LoaderService } from './loader.service'; -import { environment } from 'src/environments/environment'; - @Injectable({ providedIn: 'root', }) -export class ResourceTypeService { +export class ResourceGuidService { private jsonApiService = inject(JsonApiService); private loaderService = inject(LoaderService); - getResourceType(id: string): Observable { + getResourceById(id: string): Observable { const baseUrl = `${environment.apiUrl}/guids/${id}/`; this.loaderService.show(); return this.jsonApiService.get(baseUrl).pipe( - map((res) => (res.data.type === 'nodes' ? ResourceType.Project : ResourceType.Registration)), + map( + (res) => + ({ + id: res.data.id, + type: res.data.type, + }) as CurrentResource + ), finalize(() => this.loaderService.hide()) ); } diff --git a/src/app/shared/stores/resource-type/resource-type.actions.ts b/src/app/shared/stores/current-resource/current-resource.actions.ts similarity index 57% rename from src/app/shared/stores/resource-type/resource-type.actions.ts rename to src/app/shared/stores/current-resource/current-resource.actions.ts index 5e22ef0e3..798f72b4c 100644 --- a/src/app/shared/stores/resource-type/resource-type.actions.ts +++ b/src/app/shared/stores/current-resource/current-resource.actions.ts @@ -1,18 +1,15 @@ -import { ResourceType } from '@shared/enums'; +import { CurrentResource } from '@osf/shared/models'; -export class SetResourceType { - static readonly type = '[ResourceType] Set Resource Type'; - constructor( - public resourceId: string, - public resourceType: ResourceType - ) {} -} - -export class GetResourceType { +export class GetResource { static readonly type = '[ResourceType] Get Resource Type'; constructor(public resourceId: string) {} } +export class SetResource { + static readonly type = '[ResourceType] Set Resource Type'; + constructor(public resource: CurrentResource) {} +} + export class ClearResourceType { static readonly type = '[ResourceType] Clear Resource Type'; } 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..53fae9a91 --- /dev/null +++ b/src/app/shared/stores/current-resource/current-resource.selectors.ts @@ -0,0 +1,24 @@ +import { Selector } from '@ngxs/store'; + +import { CurrentResource } from '@osf/shared/models'; +import { CurrentResourceType } from '@shared/enums'; + +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; + } + + @Selector([CurrentResourceState]) + static isCurrentResourceProject(state: CurrentResourceStateModel): boolean { + return state.currentResource.data?.type === CurrentResourceType.Project; + } + + @Selector([CurrentResourceState]) + static isCurrentResourceRegistry(state: CurrentResourceStateModel): boolean { + return state.currentResource.data?.type === CurrentResourceType.Registration; + } +} 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..491dc185a --- /dev/null +++ b/src/app/shared/stores/current-resource/current-resource.state.ts @@ -0,0 +1,66 @@ +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 { ClearResourceType, GetResource, SetResource } 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)) + ); + } + + @Action(SetResource) + setResourceType(ctx: StateContext, action: SetResource) { + ctx.patchState({ + currentResource: { + data: action.resource, + isLoading: false, + error: null, + }, + }); + } + + @Action(ClearResourceType) + clearResourceType(ctx: StateContext) { + ctx.setState(CURRENT_RESOURCE_DEFAULTS); + } +} 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 3e6a498a7..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'; @@ -11,7 +12,6 @@ export * from './my-resources'; export * from './node-links'; export * from './projects'; export * from './regions'; -export * from './resource-type'; export * from './subjects'; export * from './view-only-links'; export * from './wiki'; diff --git a/src/app/shared/stores/resource-type/index.ts b/src/app/shared/stores/resource-type/index.ts deleted file mode 100644 index 41a46a9d3..000000000 --- a/src/app/shared/stores/resource-type/index.ts +++ /dev/null @@ -1,4 +0,0 @@ -export * from './resource-type.actions'; -export * from './resource-type.model'; -export * from './resource-type.selectors'; -export * from './resource-type.state'; diff --git a/src/app/shared/stores/resource-type/resource-type.model.ts b/src/app/shared/stores/resource-type/resource-type.model.ts deleted file mode 100644 index 0edca7095..000000000 --- a/src/app/shared/stores/resource-type/resource-type.model.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { ResourceType } from '@shared/enums'; -import { AsyncStateModel } from '@shared/models/store'; - -export interface ResourceTypeStateModel { - currentResourceType: AsyncStateModel; - currentResourceId: string | null; -} - -export const RESOURCE_TYPE_DEFAULTS: ResourceTypeStateModel = { - currentResourceType: { - data: null, - isLoading: false, - error: null, - }, - currentResourceId: null, -}; diff --git a/src/app/shared/stores/resource-type/resource-type.selectors.ts b/src/app/shared/stores/resource-type/resource-type.selectors.ts deleted file mode 100644 index 660ba2664..000000000 --- a/src/app/shared/stores/resource-type/resource-type.selectors.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { Selector } from '@ngxs/store'; - -import { ResourceType } from '@shared/enums'; - -import { ResourceTypeStateModel } from './resource-type.model'; -import { ResourceTypeState } from './resource-type.state'; - -export class ResourceTypeSelectors { - @Selector([ResourceTypeState]) - static getCurrentResourceType(state: ResourceTypeStateModel): ResourceType | null { - return state.currentResourceType.data; - } - - @Selector([ResourceTypeState]) - static getCurrentResourceId(state: ResourceTypeStateModel): string | null { - return state.currentResourceId; - } - - @Selector([ResourceTypeState]) - static isCurrentResourceProject(state: ResourceTypeStateModel): boolean { - return state.currentResourceType.data === ResourceType.Project; - } - - @Selector([ResourceTypeState]) - static isCurrentResourceRegistry(state: ResourceTypeStateModel): boolean { - return state.currentResourceType.data === ResourceType.Registration; - } -} diff --git a/src/app/shared/stores/resource-type/resource-type.state.ts b/src/app/shared/stores/resource-type/resource-type.state.ts deleted file mode 100644 index c963192d0..000000000 --- a/src/app/shared/stores/resource-type/resource-type.state.ts +++ /dev/null @@ -1,76 +0,0 @@ -import { Action, State, StateContext } from '@ngxs/store'; - -import { catchError, of, tap } from 'rxjs'; - -import { inject, Injectable } from '@angular/core'; - -import { ResourceTypeService } from '@osf/shared/services/resource-type.service'; - -import { ClearResourceType, GetResourceType, SetResourceType } from './resource-type.actions'; -import { RESOURCE_TYPE_DEFAULTS, ResourceTypeStateModel } from './resource-type.model'; - -@State({ - name: 'resourceType', - defaults: RESOURCE_TYPE_DEFAULTS, -}) -@Injectable() -export class ResourceTypeState { - private resourceTypeService = inject(ResourceTypeService); - - @Action(SetResourceType) - setResourceType(ctx: StateContext, action: SetResourceType) { - ctx.patchState({ - currentResourceType: { - data: action.resourceType, - isLoading: false, - error: null, - }, - currentResourceId: action.resourceId, - }); - } - - @Action(GetResourceType) - getResourceType(ctx: StateContext, action: GetResourceType) { - const state = ctx.getState(); - - if (state.currentResourceId === action.resourceId && state.currentResourceType.data) { - return; - } - - ctx.patchState({ - currentResourceType: { - ...state.currentResourceType, - isLoading: true, - error: null, - }, - currentResourceId: action.resourceId, - }); - - return this.resourceTypeService.getResourceType(action.resourceId).pipe( - tap((resourceType) => { - ctx.patchState({ - currentResourceType: { - data: resourceType, - isLoading: false, - error: null, - }, - }); - }), - catchError((error) => { - ctx.patchState({ - currentResourceType: { - data: null, - isLoading: false, - error, - }, - }); - return of(null); - }) - ); - } - - @Action(ClearResourceType) - clearResourceType(ctx: StateContext) { - ctx.setState(RESOURCE_TYPE_DEFAULTS); - } -} From 40269dcbab40eb4f1eabaabcc019365981bc4161 Mon Sep 17 00:00:00 2001 From: nsemets Date: Wed, 20 Aug 2025 18:33:23 +0300 Subject: [PATCH 5/6] feat(guid-routing): updated routing for project and registries --- src/app/app.routes.ts | 42 ++---- .../components/nav-menu/nav-menu.component.ts | 11 +- src/app/core/constants/nav-items.constant.ts | 8 +- .../core/constants/ngxs-states.constant.ts | 3 +- src/app/core/guards/index.ts | 2 + src/app/core/guards/is-project.guard.ts | 57 ++++++++ src/app/core/guards/is-registry.guard.ts | 57 ++++++++ src/app/core/helpers/nav-menu.helper.ts | 4 +- .../add-to-collection.component.ts | 2 +- .../file-detail/file-detail.component.ts | 3 +- .../pages/dashboard/dashboard.component.ts | 2 +- .../meeting-details.component.html | 2 +- .../features/moderation/components/index.ts | 2 +- ...print-recent-activity-list.component.html} | 0 ...print-recent-activity-list.component.scss} | 0 ...nt-recent-activity-list.component.spec.ts} | 16 +-- ...reprint-recent-activity-list.component.ts} | 11 +- .../registry-submission-item.component.html | 2 +- .../my-preprint-reviewing.component.html | 4 +- .../my-preprint-reviewing.component.spec.ts | 4 +- .../my-preprint-reviewing.component.ts | 4 +- .../my-projects/my-projects.component.ts | 4 +- .../project/analytics/analytics.component.ts | 3 +- .../view-duplicates.component.html | 6 +- .../view-duplicates.component.ts | 4 +- .../analytics/services/analytics.service.ts | 2 +- .../features/project/analytics/test-data.ts | 135 ------------------ .../add-component-dialog.component.html | 4 +- .../add-component-dialog.component.scss | 4 +- .../linked-resources.component.html | 2 +- .../linked-resources.component.ts | 10 +- .../overview-components.component.html | 2 +- .../overview-components.component.ts | 4 +- .../overview-toolbar.component.html | 2 +- .../recent-activity.component.html | 2 +- .../recent-activity.component.scss | 6 +- src/app/features/project/project.routes.ts | 4 +- ...ct-detail-setting-accordion.component.html | 2 +- .../registry-components.component.ts | 2 +- .../registry-links.component.ts | 4 +- .../registry-overview.component.ts | 3 +- src/app/features/registry/registry.routes.ts | 26 +++- .../data-resources.component.html | 10 +- .../data-resources.component.ts | 7 +- .../registration-card.component.html | 2 +- .../resource-card/resource-card.component.ts | 2 +- .../wiki/wiki-list/wiki-list.component.html | 20 +-- .../wiki/wiki-list/wiki-list.component.scss | 9 +- .../wiki/wiki-list/wiki-list.component.ts | 13 +- src/app/shared/guards/index.ts | 2 - src/app/shared/guards/is-project.guard.ts | 39 ----- src/app/shared/guards/is-registry.guard.ts | 39 ----- 52 files changed, 253 insertions(+), 357 deletions(-) create mode 100644 src/app/core/guards/is-project.guard.ts create mode 100644 src/app/core/guards/is-registry.guard.ts rename src/app/features/moderation/components/{recent-activity-list/recent-activity-list.component.html => preprint-recent-activity-list/preprint-recent-activity-list.component.html} (100%) rename src/app/features/moderation/components/{recent-activity-list/recent-activity-list.component.scss => preprint-recent-activity-list/preprint-recent-activity-list.component.scss} (100%) rename src/app/features/moderation/components/{recent-activity-list/recent-activity-list.component.spec.ts => preprint-recent-activity-list/preprint-recent-activity-list.component.spec.ts} (57%) rename src/app/features/moderation/components/{recent-activity-list/recent-activity-list.component.ts => preprint-recent-activity-list/preprint-recent-activity-list.component.ts} (77%) delete mode 100644 src/app/features/project/analytics/test-data.ts delete mode 100644 src/app/shared/guards/is-project.guard.ts delete mode 100644 src/app/shared/guards/is-registry.guard.ts diff --git a/src/app/app.routes.ts b/src/app/app.routes.ts index fa0726a2b..c9072a665 100644 --- a/src/app/app.routes.ts +++ b/src/app/app.routes.ts @@ -2,9 +2,11 @@ import { provideStates } from '@ngxs/store'; import { Routes } from '@angular/router'; -import { BookmarksState, ProjectsState, ResourceTypeState } from '@shared/stores'; +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'; @@ -15,8 +17,6 @@ import { FilesHandlers } from './features/registries/store/handlers/files.handle import { ResourceFiltersOptionsState } from './features/search/components/filters/store'; import { ResourceFiltersState } from './features/search/components/resource-filters/store'; import { SearchState } from './features/search/store'; -import { isProjectGuard } from './shared/guards/is-project.guard'; -import { isRegistryGuard } from './shared/guards/is-registry.guard'; import { LicensesService } from './shared/services'; export const routes: Routes = [ @@ -100,24 +100,6 @@ export const routes: Routes = [ ), providers: [provideStates([PreprintState])], }, - { - path: ':id', - canMatch: [authGuard, isProjectGuard], - loadChildren: () => import('./features/project/project.routes').then((m) => m.projectRoutes), - providers: [provideStates([ProjectsState, BookmarksState, ResourceTypeState])], - }, - { - path: ':id', - canMatch: [authGuard, isRegistryGuard], - loadChildren: () => import('./features/registry/registry.routes').then((m) => m.registryRoutes), - providers: [provideStates([BookmarksState, ResourceTypeState])], - }, - { - 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), @@ -133,12 +115,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), @@ -191,6 +167,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..7908ea476 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,8 @@ 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.Project, + isRegistry: this.currentResource()?.type === CurrentResourceType.Registration, isPreprint: this.isPreprintRoute(), preprintReviewsPageVisible: this.canUserViewReviews(), isCollections: this.isCollectionsRoute() || false, @@ -66,9 +69,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 +79,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..802b2f0e5 100644 --- a/src/app/core/constants/nav-items.constant.ts +++ b/src/app/core/constants/nav-items.constant.ts @@ -37,7 +37,7 @@ export const PROJECT_MENU_ITEMS: MenuItem[] = [ 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', @@ -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 5a4b4b6d5..be225a80c 100644 --- a/src/app/core/constants/ngxs-states.constant.ts +++ b/src/app/core/constants/ngxs-states.constant.ts @@ -4,7 +4,7 @@ import { FilesState } from '@osf/features/files/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 { 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'; @@ -22,4 +22,5 @@ export const STATES = [ 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..6fb6f1783 --- /dev/null +++ b/src/app/core/guards/is-project.guard.ts @@ -0,0 +1,57 @@ +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.File) { + router.navigate(['/files', id]); + return false; + } + + if (currentResource.type === CurrentResourceType.User) { + router.navigate(['/user', id]); + return false; + } + + return currentResource.type === CurrentResourceType.Project; + } + + 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.File) { + router.navigate(['/files', id]); + return false; + } + + if (resource.type === CurrentResourceType.User) { + router.navigate(['/user', id]); + return false; + } + + return resource.type === CurrentResourceType.Project; + }) + ); +}; 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..caf62fbb6 --- /dev/null +++ b/src/app/core/guards/is-registry.guard.ts @@ -0,0 +1,57 @@ +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.File) { + router.navigate(['/files', id]); + return false; + } + + if (currentResource.type === CurrentResourceType.User) { + router.navigate(['/user', id]); + return false; + } + + return currentResource.type === CurrentResourceType.Registration; + } + + 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.File) { + router.navigate(['/files', id]); + return false; + } + + if (resource.type === CurrentResourceType.User) { + router.navigate(['/user', id]); + return false; + } + + return resource.type === CurrentResourceType.Registration; + }) + ); +}; 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/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 8f25ca2a1..b2ae27c38 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 598c0fba6..1eabc662f 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 be3a9ea01..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 { CitationsState, 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'; @@ -18,6 +28,7 @@ export const registryRoutes: Routes = [ { path: '', component: RegistryComponent, + providers: [provideStates([RegistryOverviewState])], children: [ { path: '', @@ -28,13 +39,20 @@ export const registryRoutes: Routes = [ path: 'overview', loadComponent: () => import('./pages/registry-overview/registry-overview.component').then((c) => c.RegistryOverviewComponent), - providers: [provideStates([RegistryOverviewState, CitationsState])], + 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/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 2dfae9848..a72cdc07d 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/guards/index.ts b/src/app/shared/guards/index.ts index dc1d8b9af..c0ba5d4a2 100644 --- a/src/app/shared/guards/index.ts +++ b/src/app/shared/guards/index.ts @@ -1,3 +1 @@ export { ConfirmLeavingGuard } from './confirm-leaving.guard'; -export { isProjectGuard } from './is-project.guard'; -export { isRegistryGuard } from './is-registry.guard'; diff --git a/src/app/shared/guards/is-project.guard.ts b/src/app/shared/guards/is-project.guard.ts deleted file mode 100644 index 3a8cbd8f0..000000000 --- a/src/app/shared/guards/is-project.guard.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { Store } from '@ngxs/store'; - -import { lastValueFrom } from 'rxjs'; - -import { inject } from '@angular/core'; -import { CanMatchFn, Route, UrlSegment } from '@angular/router'; - -import { ResourceType } from '../enums'; -import { ResourceTypeService } from '../services/resource-type.service'; -import { ResourceTypeSelectors, SetResourceType } from '../stores'; - -export const isProjectGuard: CanMatchFn = async (route: Route, segments: UrlSegment[]) => { - const resourceTypeService = inject(ResourceTypeService); - const store = inject(Store); - - const id = segments[0]?.path; - - if (!id) { - return false; - } - - const currentResourceId = store.selectSnapshot(ResourceTypeSelectors.getCurrentResourceId); - const currentResourceType = store.selectSnapshot(ResourceTypeSelectors.getCurrentResourceType); - - if (currentResourceId === id && currentResourceType !== null) { - return currentResourceType === ResourceType.Project; - } - - try { - const resourceType = await lastValueFrom(resourceTypeService.getResourceType(id)); - const isProject = resourceType === ResourceType.Project; - - store.dispatch(new SetResourceType(id, resourceType)); - - return isProject; - } catch { - return false; - } -}; diff --git a/src/app/shared/guards/is-registry.guard.ts b/src/app/shared/guards/is-registry.guard.ts deleted file mode 100644 index b9d0004a7..000000000 --- a/src/app/shared/guards/is-registry.guard.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { Store } from '@ngxs/store'; - -import { lastValueFrom } from 'rxjs'; - -import { inject } from '@angular/core'; -import { CanMatchFn, Route, UrlSegment } from '@angular/router'; - -import { ResourceType } from '../enums'; -import { ResourceTypeService } from '../services/resource-type.service'; -import { ResourceTypeSelectors, SetResourceType } from '../stores'; - -export const isRegistryGuard: CanMatchFn = async (route: Route, segments: UrlSegment[]) => { - const resourceTypeService = inject(ResourceTypeService); - const store = inject(Store); - - const id = segments[0]?.path; - - if (!id) { - return false; - } - - const currentResourceId = store.selectSnapshot(ResourceTypeSelectors.getCurrentResourceId); - const currentResourceType = store.selectSnapshot(ResourceTypeSelectors.getCurrentResourceType); - - if (currentResourceId === id && currentResourceType !== null) { - return currentResourceType === ResourceType.Registration; - } - - try { - const resourceType = await lastValueFrom(resourceTypeService.getResourceType(id)); - const isRegistry = resourceType === ResourceType.Registration; - - store.dispatch(new SetResourceType(id, resourceType)); - - return isRegistry; - } catch { - return false; - } -}; From f8fdb380e27bd5a8fd4ccbbf572e5cb5b0ca5a14 Mon Sep 17 00:00:00 2001 From: nsemets Date: Thu, 21 Aug 2025 12:28:22 +0300 Subject: [PATCH 6/6] feat(guid-routing): added logic for files and preprints --- .../components/nav-menu/nav-menu.component.ts | 8 +++-- src/app/core/constants/nav-items.constant.ts | 4 +-- src/app/core/guards/is-project.guard.ts | 32 ++++++++++++------- src/app/core/guards/is-registry.guard.ts | 32 ++++++++++++------- src/app/shared/enums/resource-type.enum.ts | 10 +++--- .../general-institution.mapper.ts | 2 +- .../shared/models/current-resource.model.ts | 1 + .../models/guid-response-json-api.model.ts | 16 ++++++++++ .../shared/services/resource-guid.service.ts | 13 ++++++-- .../current-resource.actions.ts | 11 ------- .../current-resource.selectors.ts | 11 ------- .../current-resource.state.ts | 18 +---------- 12 files changed, 84 insertions(+), 74 deletions(-) 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 7908ea476..87367469b 100644 --- a/src/app/core/components/nav-menu/nav-menu.component.ts +++ b/src/app/core/components/nav-menu/nav-menu.component.ts @@ -44,8 +44,12 @@ export class NavMenuComponent { const routeContext: RouteContext = { resourceId: this.currentResourceId(), providerId: this.currentProviderId(), - isProject: this.currentResource()?.type === CurrentResourceType.Project, - isRegistry: this.currentResource()?.type === CurrentResourceType.Registration, + 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, diff --git a/src/app/core/constants/nav-items.constant.ts b/src/app/core/constants/nav-items.constant.ts index 802b2f0e5..ad6efa0a9 100644 --- a/src/app/core/constants/nav-items.constant.ts +++ b/src/app/core/constants/nav-items.constant.ts @@ -30,7 +30,7 @@ export const PROJECT_MENU_ITEMS: MenuItem[] = [ label: 'navigation.files', routerLink: 'files', visible: true, - routerLinkActiveOptions: { exact: true }, + routerLinkActiveOptions: { exact: false }, }, { id: 'project-wiki', @@ -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', diff --git a/src/app/core/guards/is-project.guard.ts b/src/app/core/guards/is-project.guard.ts index 6fb6f1783..0f78310ef 100644 --- a/src/app/core/guards/is-project.guard.ts +++ b/src/app/core/guards/is-project.guard.ts @@ -21,17 +21,22 @@ export const isProjectGuard: CanMatchFn = (route: Route, segments: UrlSegment[]) const currentResource = store.selectSnapshot(CurrentResourceSelectors.getCurrentResource); if (currentResource && currentResource.id === id) { - if (currentResource.type === CurrentResourceType.File) { - router.navigate(['/files', id]); - return false; + 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.User) { - router.navigate(['/user', id]); + if (currentResource.type === CurrentResourceType.Users) { + router.navigate(['/profile', id]); return false; } - return currentResource.type === CurrentResourceType.Project; + return currentResource.type === CurrentResourceType.Projects; } return store.dispatch(new GetResource(id)).pipe( @@ -41,17 +46,22 @@ export const isProjectGuard: CanMatchFn = (route: Route, segments: UrlSegment[]) return false; } - if (resource.type === CurrentResourceType.File) { - router.navigate(['/files', 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.User) { + if (resource.type === CurrentResourceType.Users) { router.navigate(['/user', id]); return false; } - return resource.type === CurrentResourceType.Project; + 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 index caf62fbb6..0f592b553 100644 --- a/src/app/core/guards/is-registry.guard.ts +++ b/src/app/core/guards/is-registry.guard.ts @@ -21,17 +21,22 @@ export const isRegistryGuard: CanMatchFn = (route: Route, segments: UrlSegment[] const currentResource = store.selectSnapshot(CurrentResourceSelectors.getCurrentResource); if (currentResource && currentResource.id === id) { - if (currentResource.type === CurrentResourceType.File) { - router.navigate(['/files', id]); - return false; + 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.User) { + if (currentResource.type === CurrentResourceType.Users) { router.navigate(['/user', id]); return false; } - return currentResource.type === CurrentResourceType.Registration; + return currentResource.type === CurrentResourceType.Registrations; } return store.dispatch(new GetResource(id)).pipe( @@ -41,17 +46,22 @@ export const isRegistryGuard: CanMatchFn = (route: Route, segments: UrlSegment[] return false; } - if (resource.type === CurrentResourceType.File) { - router.navigate(['/files', 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.User) { - router.navigate(['/user', id]); + if (resource.type === CurrentResourceType.Users) { + router.navigate(['/profile', id]); return false; } - return resource.type === CurrentResourceType.Registration; + return resource.type === CurrentResourceType.Registrations; }) ); }; diff --git a/src/app/shared/enums/resource-type.enum.ts b/src/app/shared/enums/resource-type.enum.ts index 0f4094070..72ef89e77 100644 --- a/src/app/shared/enums/resource-type.enum.ts +++ b/src/app/shared/enums/resource-type.enum.ts @@ -11,9 +11,9 @@ export enum ResourceType { } export enum CurrentResourceType { - User = 'users', - File = 'files', - Project = 'nodes', - Registration = 'registrations', - Preprint = 'preprints', + 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 index 452e30143..98ad6ad25 100644 --- a/src/app/shared/models/current-resource.model.ts +++ b/src/app/shared/models/current-resource.model.ts @@ -1,4 +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 index 60101769f..438895a7b 100644 --- a/src/app/shared/models/guid-response-json-api.model.ts +++ b/src/app/shared/models/guid-response-json-api.model.ts @@ -5,4 +5,20 @@ 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/services/resource-guid.service.ts b/src/app/shared/services/resource-guid.service.ts index dc1d86e4f..ee557ad30 100644 --- a/src/app/shared/services/resource-guid.service.ts +++ b/src/app/shared/services/resource-guid.service.ts @@ -4,11 +4,13 @@ import { inject, Injectable } from '@angular/core'; import { CurrentResource, GuidedResponseJsonApi } from '@osf/shared/models'; -import { environment } from '../../../environments/environment'; +import { CurrentResourceType } from '../enums'; import { JsonApiService } from './json-api.service'; import { LoaderService } from './loader.service'; +import { environment } from 'src/environments/environment'; + @Injectable({ providedIn: 'root', }) @@ -25,8 +27,13 @@ export class ResourceGuidService { map( (res) => ({ - id: res.data.id, - type: res.data.type, + 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 index 798f72b4c..ed63dc444 100644 --- a/src/app/shared/stores/current-resource/current-resource.actions.ts +++ b/src/app/shared/stores/current-resource/current-resource.actions.ts @@ -1,15 +1,4 @@ -import { CurrentResource } from '@osf/shared/models'; - export class GetResource { static readonly type = '[ResourceType] Get Resource Type'; constructor(public resourceId: string) {} } - -export class SetResource { - static readonly type = '[ResourceType] Set Resource Type'; - constructor(public resource: CurrentResource) {} -} - -export class ClearResourceType { - static readonly type = '[ResourceType] Clear Resource Type'; -} diff --git a/src/app/shared/stores/current-resource/current-resource.selectors.ts b/src/app/shared/stores/current-resource/current-resource.selectors.ts index 53fae9a91..5b321c247 100644 --- a/src/app/shared/stores/current-resource/current-resource.selectors.ts +++ b/src/app/shared/stores/current-resource/current-resource.selectors.ts @@ -1,7 +1,6 @@ import { Selector } from '@ngxs/store'; import { CurrentResource } from '@osf/shared/models'; -import { CurrentResourceType } from '@shared/enums'; import { CurrentResourceStateModel } from './current-resource.model'; import { CurrentResourceState } from './current-resource.state'; @@ -11,14 +10,4 @@ export class CurrentResourceSelectors { static getCurrentResource(state: CurrentResourceStateModel): CurrentResource | null { return state.currentResource.data; } - - @Selector([CurrentResourceState]) - static isCurrentResourceProject(state: CurrentResourceStateModel): boolean { - return state.currentResource.data?.type === CurrentResourceType.Project; - } - - @Selector([CurrentResourceState]) - static isCurrentResourceRegistry(state: CurrentResourceStateModel): boolean { - return state.currentResource.data?.type === CurrentResourceType.Registration; - } } diff --git a/src/app/shared/stores/current-resource/current-resource.state.ts b/src/app/shared/stores/current-resource/current-resource.state.ts index 491dc185a..7114353ef 100644 --- a/src/app/shared/stores/current-resource/current-resource.state.ts +++ b/src/app/shared/stores/current-resource/current-resource.state.ts @@ -7,7 +7,7 @@ import { inject, Injectable } from '@angular/core'; import { handleSectionError } from '@osf/shared/helpers'; import { ResourceGuidService } from '@osf/shared/services'; -import { ClearResourceType, GetResource, SetResource } from './current-resource.actions'; +import { GetResource } from './current-resource.actions'; import { CURRENT_RESOURCE_DEFAULTS, CurrentResourceStateModel } from './current-resource.model'; @State({ @@ -47,20 +47,4 @@ export class CurrentResourceState { catchError((error) => handleSectionError(ctx, 'currentResource', error)) ); } - - @Action(SetResource) - setResourceType(ctx: StateContext, action: SetResource) { - ctx.patchState({ - currentResource: { - data: action.resource, - isLoading: false, - error: null, - }, - }); - } - - @Action(ClearResourceType) - clearResourceType(ctx: StateContext) { - ctx.setState(CURRENT_RESOURCE_DEFAULTS); - } }