From fbffcca945c6b6d9e86cf9a6e7e6cbd6d181a28c Mon Sep 17 00:00:00 2001 From: Yury Bondarenko Date: Fri, 7 Feb 2025 16:35:17 +0100 Subject: [PATCH] Resolve post-merge issues - Menu providers weren't included because main configuration is no longer a module - Route definitions didn't get merged because they're no longer modules - Removed old resolver & service (they're providers now) --- src/app/app-routes.ts | 2 - src/app/app.config.ts | 5 + .../collection-page/collection-page-routes.ts | 25 +- .../community-page/community-page-routes.ts | 25 +- src/app/item-page/item-page-routes.ts | 33 +- src/app/menuResolver.ts | 21 -- .../dso-edit-menu-resolver.service.ts | 324 ------------------ .../statistics-page/statistics-page-routes.ts | 13 + 8 files changed, 35 insertions(+), 413 deletions(-) delete mode 100644 src/app/menuResolver.ts delete mode 100644 src/app/shared/dso-page/dso-edit-menu-resolver.service.ts diff --git a/src/app/app-routes.ts b/src/app/app-routes.ts index 51101f5a2d..db71c866f0 100644 --- a/src/app/app-routes.ts +++ b/src/app/app-routes.ts @@ -34,7 +34,6 @@ import { forgotPasswordCheckGuard } from './core/rest-property/forgot-password-c import { ServerCheckGuard } from './core/server-check/server-check.guard'; import { ThemedForbiddenComponent } from './forbidden/themed-forbidden.component'; import { ITEM_MODULE_PATH } from './item-page/item-page-routing-paths'; -import { menuResolver } from './menuResolver'; import { provideSuggestionNotificationsState } from './notifications/provide-suggestion-notifications-state'; import { ThemedPageErrorComponent } from './page-error/themed-page-error.component'; import { ThemedPageInternalServerErrorComponent } from './page-internal-server-error/themed-page-internal-server-error.component'; @@ -50,7 +49,6 @@ export const APP_ROUTES: Route[] = [ path: '', canActivate: [authBlockingGuard], canActivateChild: [ServerCheckGuard], - resolve: [menuResolver], children: [ { path: '', redirectTo: '/home', pathMatch: 'full' }, { diff --git a/src/app/app.config.ts b/src/app/app.config.ts index 77b29206cb..15d7cf4952 100644 --- a/src/app/app.config.ts +++ b/src/app/app.config.ts @@ -38,6 +38,7 @@ import { StoreDevModules } from '../config/store/devtools'; import { environment } from '../environments/environment'; import { EagerThemesModule } from '../themes/eager-themes.module'; import { appEffects } from './app.effects'; +import { MENUS } from './app.menus'; import { appMetaReducers, debugMetaReducers, @@ -156,6 +157,10 @@ export const commonAppConfig: ApplicationConfig = { }, // register the dynamic matcher used by form. MUST be provided by the app module ...DYNAMIC_MATCHER_PROVIDERS, + + // DI-composable menus + ...MENUS, + provideCore(), ], }; diff --git a/src/app/collection-page/collection-page-routes.ts b/src/app/collection-page/collection-page-routes.ts index e20e3ba8af..ffac37c0d1 100644 --- a/src/app/collection-page/collection-page-routes.ts +++ b/src/app/collection-page/collection-page-routes.ts @@ -8,16 +8,14 @@ import { communityBreadcrumbResolver } from '../core/breadcrumbs/community-bread import { i18nBreadcrumbResolver } from '../core/breadcrumbs/i18n-breadcrumb.resolver'; import { ComcolBrowseByComponent } from '../shared/comcol/sections/comcol-browse-by/comcol-browse-by.component'; import { ComcolSearchSectionComponent } from '../shared/comcol/sections/comcol-search-section/comcol-search-section.component'; -import { dsoEditMenuResolver } from '../shared/dso-page/dso-edit-menu.resolver'; -import { LinkMenuItemModel } from '../shared/menu/menu-item/models/link.model'; -import { MenuItemType } from '../shared/menu/menu-item-type.model'; -import { collectionPageResolver } from './collection-page.resolver'; +import { MenuRoute } from '../shared/menu/menu-route.model'; import { collectionPageAdministratorGuard } from './collection-page-administrator.guard'; import { COLLECTION_CREATE_PATH, COLLECTION_EDIT_PATH, ITEMTEMPLATE_PATH, } from './collection-page-routing-paths'; +import { collectionPageResolver } from './collection-page.resolver'; import { CreateCollectionPageComponent } from './create-collection-page/create-collection-page.component'; import { createCollectionPageGuard } from './create-collection-page/create-collection-page.guard'; import { DeleteCollectionPageComponent } from './delete-collection-page/delete-collection-page.component'; @@ -82,8 +80,8 @@ export const ROUTES: Route[] = [ { path: '', component: ThemedCollectionPageComponent, - resolve: { - menu: dsoEditMenuResolver, + data: { + menuRoute: MenuRoute.SIMPLE_COLLECTION_PAGE, }, children: [ { @@ -104,20 +102,5 @@ export const ROUTES: Route[] = [ ], }, ], - data: { - menu: { - public: [{ - id: 'statistics_collection_:id', - active: true, - visible: true, - index: 2, - model: { - type: MenuItemType.LINK, - text: 'menu.section.statistics', - link: 'statistics/collections/:id/', - } as LinkMenuItemModel, - }], - }, - }, }, ]; diff --git a/src/app/community-page/community-page-routes.ts b/src/app/community-page/community-page-routes.ts index 2c8a7942a4..588cc59799 100644 --- a/src/app/community-page/community-page-routes.ts +++ b/src/app/community-page/community-page-routes.ts @@ -7,15 +7,13 @@ import { communityBreadcrumbResolver } from '../core/breadcrumbs/community-bread import { i18nBreadcrumbResolver } from '../core/breadcrumbs/i18n-breadcrumb.resolver'; import { ComcolBrowseByComponent } from '../shared/comcol/sections/comcol-browse-by/comcol-browse-by.component'; import { ComcolSearchSectionComponent } from '../shared/comcol/sections/comcol-search-section/comcol-search-section.component'; -import { dsoEditMenuResolver } from '../shared/dso-page/dso-edit-menu.resolver'; -import { LinkMenuItemModel } from '../shared/menu/menu-item/models/link.model'; -import { MenuItemType } from '../shared/menu/menu-item-type.model'; -import { communityPageResolver } from './community-page.resolver'; +import { MenuRoute } from '../shared/menu/menu-route.model'; import { communityPageAdministratorGuard } from './community-page-administrator.guard'; import { COMMUNITY_CREATE_PATH, COMMUNITY_EDIT_PATH, } from './community-page-routing-paths'; +import { communityPageResolver } from './community-page.resolver'; import { CreateCommunityPageComponent } from './create-community-page/create-community-page.component'; import { createCommunityPageGuard } from './create-community-page/create-community-page.guard'; import { DeleteCommunityPageComponent } from './delete-community-page/delete-community-page.component'; @@ -69,8 +67,8 @@ export const ROUTES: Route[] = [ { path: '', component: ThemedCommunityPageComponent, - resolve: { - menu: dsoEditMenuResolver, + data: { + menuRoute: MenuRoute.SIMPLE_COMMUNITY_PAGE, }, children: [ { @@ -100,20 +98,5 @@ export const ROUTES: Route[] = [ ], }, ], - data: { - menu: { - public: [{ - id: 'statistics_community_:id', - active: true, - visible: true, - index: 2, - model: { - type: MenuItemType.LINK, - text: 'menu.section.statistics', - link: 'statistics/communities/:id/', - } as LinkMenuItemModel, - }], - }, - }, }, ]; diff --git a/src/app/item-page/item-page-routes.ts b/src/app/item-page/item-page-routes.ts index 854d66fabe..1b63ecb520 100644 --- a/src/app/item-page/item-page-routes.ts +++ b/src/app/item-page/item-page-routes.ts @@ -3,23 +3,21 @@ import { Route } from '@angular/router'; import { REQUEST_COPY_MODULE_PATH } from '../app-routing-paths'; import { authenticatedGuard } from '../core/auth/authenticated.guard'; import { itemBreadcrumbResolver } from '../core/breadcrumbs/item-breadcrumb.resolver'; -import { dsoEditMenuResolver } from '../shared/dso-page/dso-edit-menu.resolver'; -import { LinkMenuItemModel } from '../shared/menu/menu-item/models/link.model'; -import { MenuItemType } from '../shared/menu/menu-item-type.model'; +import { MenuRoute } from '../shared/menu/menu-route.model'; import { BitstreamRequestACopyPageComponent } from './bitstreams/request-a-copy/bitstream-request-a-copy-page.component'; import { UploadBitstreamComponent } from './bitstreams/upload/upload-bitstream.component'; import { ThemedFullItemPageComponent } from './full/themed-full-item-page.component'; -import { itemPageResolver } from './item-page.resolver'; import { ITEM_EDIT_PATH, ORCID_PATH, UPLOAD_BITSTREAM_PATH, } from './item-page-routing-paths'; +import { itemPageResolver } from './item-page.resolver'; import { OrcidPageComponent } from './orcid-page/orcid-page.component'; import { orcidPageGuard } from './orcid-page/orcid-page.guard'; import { ThemedItemPageComponent } from './simple/themed-item-page.component'; -import { versionResolver } from './version-page/version.resolver'; import { VersionPageComponent } from './version-page/version-page/version-page.component'; +import { versionResolver } from './version-page/version.resolver'; export const ROUTES: Route[] = [ { @@ -34,16 +32,18 @@ export const ROUTES: Route[] = [ path: '', component: ThemedItemPageComponent, pathMatch: 'full', - resolve: { - menu: dsoEditMenuResolver, + data: { + menuRoute: MenuRoute.SIMPLE_ITEM_PAGE, }, + }, { path: 'full', component: ThemedFullItemPageComponent, - resolve: { - menu: dsoEditMenuResolver, + data: { + menuRoute: MenuRoute.FULL_ITEM_PAGE, }, + }, { path: ITEM_EDIT_PATH, @@ -65,21 +65,6 @@ export const ROUTES: Route[] = [ canActivate: [authenticatedGuard, orcidPageGuard], }, ], - data: { - menu: { - public: [{ - id: 'statistics_item_:id', - active: true, - visible: true, - index: 2, - model: { - type: MenuItemType.LINK, - text: 'menu.section.statistics', - link: 'statistics/items/:id/', - } as LinkMenuItemModel, - }], - }, - }, }, { path: 'version', diff --git a/src/app/menuResolver.ts b/src/app/menuResolver.ts deleted file mode 100644 index 68ad4494dd..0000000000 --- a/src/app/menuResolver.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { inject } from '@angular/core'; -import { - ActivatedRouteSnapshot, - ResolveFn, - RouterStateSnapshot, -} from '@angular/router'; -import { Observable } from 'rxjs'; - -import { MenuResolverService } from './menu-resolver.service'; - - -/** - * Initialize all menus - */ -export const menuResolver: ResolveFn = ( - route: ActivatedRouteSnapshot, - state: RouterStateSnapshot, - menuResolverService: MenuResolverService = inject(MenuResolverService), -): Observable => { - return menuResolverService.resolve(route, state); -}; diff --git a/src/app/shared/dso-page/dso-edit-menu-resolver.service.ts b/src/app/shared/dso-page/dso-edit-menu-resolver.service.ts deleted file mode 100644 index 8c4fd15b7e..0000000000 --- a/src/app/shared/dso-page/dso-edit-menu-resolver.service.ts +++ /dev/null @@ -1,324 +0,0 @@ -import { Injectable } from '@angular/core'; -import { - ActivatedRouteSnapshot, - RouterStateSnapshot, -} from '@angular/router'; -import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; -import { TranslateService } from '@ngx-translate/core'; -import { - combineLatest, - Observable, - of as observableOf, -} from 'rxjs'; -import { - map, - switchMap, -} from 'rxjs/operators'; - -import { getDSORoute } from '../../app-routing-paths'; -import { DSpaceObjectDataService } from '../../core/data/dspace-object-data.service'; -import { AuthorizationDataService } from '../../core/data/feature-authorization/authorization-data.service'; -import { FeatureID } from '../../core/data/feature-authorization/feature-id'; -import { ResearcherProfileDataService } from '../../core/profile/researcher-profile-data.service'; -import { Collection } from '../../core/shared/collection.model'; -import { Community } from '../../core/shared/community.model'; -import { Item } from '../../core/shared/item.model'; -import { - getFirstCompletedRemoteData, - getRemoteDataPayload, -} from '../../core/shared/operators'; -import { CorrectionTypeDataService } from '../../core/submission/correctiontype-data.service'; -import { URLCombiner } from '../../core/url-combiner/url-combiner'; -import { - hasNoValue, - hasValue, - isNotEmpty, -} from '../empty.util'; -import { MenuService } from '../menu/menu.service'; -import { MenuID } from '../menu/menu-id.model'; -import { LinkMenuItemModel } from '../menu/menu-item/models/link.model'; -import { OnClickMenuItemModel } from '../menu/menu-item/models/onclick.model'; -import { MenuItemType } from '../menu/menu-item-type.model'; -import { MenuSection } from '../menu/menu-section.model'; -import { NotificationsService } from '../notifications/notifications.service'; -import { SubscriptionModalComponent } from '../subscriptions/subscription-modal/subscription-modal.component'; -import { DsoVersioningModalService } from './dso-versioning-modal-service/dso-versioning-modal.service'; -import { - DsoWithdrawnReinstateModalService, - REQUEST_REINSTATE, - REQUEST_WITHDRAWN, -} from './dso-withdrawn-reinstate-service/dso-withdrawn-reinstate-modal.service'; - -/** - * Creates the menus for the dspace object pages - */ -@Injectable({ - providedIn: 'root', -}) -export class DSOEditMenuResolverService { - - constructor( - protected dSpaceObjectDataService: DSpaceObjectDataService, - protected menuService: MenuService, - protected authorizationService: AuthorizationDataService, - protected modalService: NgbModal, - protected dsoVersioningModalService: DsoVersioningModalService, - protected researcherProfileService: ResearcherProfileDataService, - protected notificationsService: NotificationsService, - protected translate: TranslateService, - protected dsoWithdrawnReinstateModalService: DsoWithdrawnReinstateModalService, - private correctionTypeDataService: CorrectionTypeDataService, - ) { - } - - /** - * Initialise all dspace object related menus - */ - resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<{ [key: string]: MenuSection[] }> { - let id = route.params.id; - if (hasNoValue(id) && hasValue(route.queryParams.scope)) { - id = route.queryParams.scope; - } - if (hasNoValue(id)) { - // If there's no ID, we're not on a DSO homepage, so pass on any pre-existing menu route data - return observableOf({ ...route.data?.menu }); - } else { - return this.dSpaceObjectDataService.findById(id, true, false).pipe( - getFirstCompletedRemoteData(), - switchMap((dsoRD) => { - if (dsoRD.hasSucceeded) { - const dso = dsoRD.payload; - return combineLatest(this.getDsoMenus(dso, route, state)).pipe( - // Menu sections are retrieved as an array of arrays and flattened into a single array - map((combinedMenus) => [].concat.apply([], combinedMenus)), - map((menus) => this.addDsoUuidToMenuIDs(menus, dso)), - map((menus) => { - return { - ...route.data?.menu, - [MenuID.DSO_EDIT]: menus, - }; - }), - ); - } else { - return observableOf({ ...route.data?.menu }); - } - }), - ); - } - } - - /** - * Return all the menus for a dso based on the route and state - */ - getDsoMenus(dso, route, state): Observable[] { - return [ - this.getItemMenu(dso), - this.getComColMenu(dso), - this.getCommonMenu(dso, state), - ]; - } - - /** - * Get the common menus between all dspace objects - */ - protected getCommonMenu(dso, state): Observable { - return combineLatest([ - this.authorizationService.isAuthorized(FeatureID.CanEditMetadata, dso.self), - ]).pipe( - map(([canEditItem]) => { - return [ - { - id: 'edit-dso', - active: false, - visible: canEditItem, - model: { - type: MenuItemType.LINK, - text: this.getDsoType(dso) + '.page.edit', - link: new URLCombiner(getDSORoute(dso), 'edit', 'metadata').toString(), - } as LinkMenuItemModel, - icon: 'pencil-alt', - index: 2, - }, - ]; - }), - ); - } - - /** - * Get item specific menus - */ - protected getItemMenu(dso): Observable { - if (dso instanceof Item) { - return combineLatest([ - this.authorizationService.isAuthorized(FeatureID.CanCreateVersion, dso.self), - this.dsoVersioningModalService.isNewVersionButtonDisabled(dso), - this.dsoVersioningModalService.getVersioningTooltipMessage(dso, 'item.page.version.hasDraft', 'item.page.version.create'), - this.authorizationService.isAuthorized(FeatureID.CanSynchronizeWithORCID, dso.self), - this.authorizationService.isAuthorized(FeatureID.CanClaimItem, dso.self), - this.correctionTypeDataService.findByItem(dso.uuid, true).pipe( - getFirstCompletedRemoteData(), - getRemoteDataPayload()), - ]).pipe( - map(([canCreateVersion, disableVersioning, versionTooltip, canSynchronizeWithOrcid, canClaimItem, correction]) => { - const isPerson = this.getDsoType(dso) === 'person'; - return [ - { - id: 'orcid-dso', - active: false, - visible: isPerson && canSynchronizeWithOrcid, - model: { - type: MenuItemType.LINK, - text: 'item.page.orcid.tooltip', - link: new URLCombiner(getDSORoute(dso), 'orcid').toString(), - } as LinkMenuItemModel, - icon: 'orcid fab fa-lg', - index: 0, - }, - { - id: 'version-dso', - active: false, - visible: canCreateVersion, - model: { - type: MenuItemType.ONCLICK, - text: versionTooltip, - disabled: disableVersioning, - function: () => { - this.dsoVersioningModalService.openCreateVersionModal(dso); - }, - } as OnClickMenuItemModel, - icon: 'code-branch', - index: 1, - }, - { - id: 'claim-dso', - active: false, - visible: isPerson && canClaimItem, - model: { - type: MenuItemType.ONCLICK, - text: 'item.page.claim.button', - function: () => { - this.claimResearcher(dso); - }, - } as OnClickMenuItemModel, - icon: 'hand-paper', - index: 3, - }, - { - id: 'withdrawn-item', - active: false, - visible: dso.isArchived && correction?.page.some((c) => c.topic === REQUEST_WITHDRAWN), - model: { - type: MenuItemType.ONCLICK, - text:'item.page.withdrawn', - function: () => { - this.dsoWithdrawnReinstateModalService.openCreateWithdrawnReinstateModal(dso, 'request-withdrawn', dso.isArchived); - }, - } as OnClickMenuItemModel, - icon: 'eye-slash', - index: 4, - }, - { - id: 'reinstate-item', - active: false, - visible: dso.isWithdrawn && correction?.page.some((c) => c.topic === REQUEST_REINSTATE), - model: { - type: MenuItemType.ONCLICK, - text:'item.page.reinstate', - function: () => { - this.dsoWithdrawnReinstateModalService.openCreateWithdrawnReinstateModal(dso, 'request-reinstate', dso.isArchived); - }, - } as OnClickMenuItemModel, - icon: 'eye', - index: 5, - }, - ]; - }), - ); - } else { - return observableOf([]); - } - } - - /** - * Get Community/Collection-specific menus - */ - protected getComColMenu(dso): Observable { - if (dso instanceof Community || dso instanceof Collection) { - return combineLatest([ - this.authorizationService.isAuthorized(FeatureID.CanSubscribe, dso.self), - ]).pipe( - map(([canSubscribe]) => { - return [ - { - id: 'subscribe', - active: false, - visible: canSubscribe, - model: { - type: MenuItemType.ONCLICK, - text: 'subscriptions.tooltip', - function: () => { - const modalRef = this.modalService.open(SubscriptionModalComponent); - modalRef.componentInstance.dso = dso; - }, - } as OnClickMenuItemModel, - icon: 'bell', - index: 4, - }, - ]; - }), - ); - } else { - return observableOf([]); - } - } - - /** - * Claim a researcher by creating a profile - * Shows notifications and/or hides the menu section on success/error - */ - protected claimResearcher(dso) { - this.researcherProfileService.createFromExternalSourceAndReturnRelatedItemId(dso.self) - .subscribe((id: string) => { - if (isNotEmpty(id)) { - this.notificationsService.success(this.translate.get('researcherprofile.success.claim.title'), - this.translate.get('researcherprofile.success.claim.body')); - this.authorizationService.invalidateAuthorizationsRequestCache(); - this.menuService.hideMenuSection(MenuID.DSO_EDIT, 'claim-dso-' + dso.uuid); - } else { - this.notificationsService.error( - this.translate.get('researcherprofile.error.claim.title'), - this.translate.get('researcherprofile.error.claim.body')); - } - }); - } - - /** - * Retrieve the dso or entity type for an object to be used in generic messages - */ - protected getDsoType(dso) { - const renderType = dso.getRenderTypes()[0]; - if (typeof renderType === 'string' || renderType instanceof String) { - return renderType.toLowerCase(); - } else { - return dso.type.toString().toLowerCase(); - } - } - - /** - * Add the dso uuid to all provided menu ids and parent ids - */ - protected addDsoUuidToMenuIDs(menus, dso) { - return menus.map((menu) => { - Object.assign(menu, { - id: menu.id + '-' + dso.uuid, - }); - if (hasValue(menu.parentID)) { - Object.assign(menu, { - parentID: menu.parentID + '-' + dso.uuid, - }); - } - return menu; - }); - } - -} diff --git a/src/app/statistics-page/statistics-page-routes.ts b/src/app/statistics-page/statistics-page-routes.ts index 69bcc6b41c..96de7b8273 100644 --- a/src/app/statistics-page/statistics-page-routes.ts +++ b/src/app/statistics-page/statistics-page-routes.ts @@ -41,6 +41,19 @@ export const ROUTES: Route[] = [ component: ThemedItemStatisticsPageComponent, canActivate: [statisticsAdministratorGuard], }, + { + path: `entities/:entityType/:id`, + resolve: { + scope: itemResolver, + breadcrumb: i18nBreadcrumbResolver, + }, + data: { + title: 'statistics.title', + breadcrumbKey: 'statistics', + }, + component: ThemedItemStatisticsPageComponent, + canActivate: [statisticsAdministratorGuard], + }, { path: `collections/:id`, resolve: {