diff --git a/src/app/admin/admin-sidebar/admin-sidebar-section/admin-sidebar-section.component.html b/src/app/admin/admin-sidebar/admin-sidebar-section/admin-sidebar-section.component.html index 7f1e8716ba..0534b7fd92 100644 --- a/src/app/admin/admin-sidebar/admin-sidebar-section/admin-sidebar-section.component.html +++ b/src/app/admin/admin-sidebar/admin-sidebar-section/admin-sidebar-section.component.html @@ -3,7 +3,7 @@ [ngClass]="{ disabled: isDisabled }" [attr.aria-disabled]="isDisabled" [attr.aria-labelledby]="'sidebarName-' + section.id" - [title]="('menu.section.icon.' + section.id) | translate" + [title]="itemModel.text | translate" [routerLink]="itemModel.link" (keyup.space)="navigate($event)" (keyup.enter)="navigate($event)" diff --git a/src/app/admin/admin-sidebar/expandable-admin-sidebar-section/expandable-admin-sidebar-section.component.html b/src/app/admin/admin-sidebar/expandable-admin-sidebar-section/expandable-admin-sidebar-section.component.html index 3edb01d621..85bfce804e 100644 --- a/src/app/admin/admin-sidebar/expandable-admin-sidebar-section/expandable-admin-sidebar-section.component.html +++ b/src/app/admin/admin-sidebar/expandable-admin-sidebar-section/expandable-admin-sidebar-section.component.html @@ -6,7 +6,7 @@ role="button" tabindex="0" [attr.aria-labelledby]="'sidebarName-' + section.id" [attr.aria-expanded]="expanded | async" - [title]="('menu.section.icon.' + section.id) | translate" + [title]="itemModel.text | translate" [class.disabled]="section.model?.disabled" (click)="toggleSection($event)" (keyup.space)="toggleSection($event)" diff --git a/src/app/app-routing.module.ts b/src/app/app-routing.module.ts index 94298c84a9..4af778433d 100644 --- a/src/app/app-routing.module.ts +++ b/src/app/app-routing.module.ts @@ -32,12 +32,10 @@ import { ReloadGuard } from './core/reload/reload.guard'; import { ServerCheckGuard } from './core/server-check/server-check.guard'; import { ThemedForbiddenComponent } from './forbidden/themed-forbidden.component'; import { ENTITY_MODULE_PATH, ITEM_MODULE_PATH } from './item-page/item-page-routing-paths'; -import { MenuResolver } from './menu.resolver'; import { ThemedPageErrorComponent } from './page-error/themed-page-error.component'; import { ThemedPageInternalServerErrorComponent } from './page-internal-server-error/themed-page-internal-server-error.component'; import { ThemedPageNotFoundComponent } from './pagenotfound/themed-pagenotfound.component'; import { PROCESS_MODULE_PATH } from './process-page/process-page-routing.paths'; -import { MenuProviderService } from './shared/menu/menu-provider.service'; // import { resolveStaticMenus } from './shared/menu/menu.resolver'; @NgModule({ diff --git a/src/app/app.menus.ts b/src/app/app.menus.ts index c0199fb8fd..7679db4c42 100644 --- a/src/app/app.menus.ts +++ b/src/app/app.menus.ts @@ -30,8 +30,7 @@ import { WorkflowMenuProvider } from './shared/menu/providers/workflow.menu'; import { COMMUNITY_MODULE_PATH } from './community-page/community-page-routing-paths'; import { COLLECTION_MODULE_PATH } from './collection-page/collection-page-routing-paths'; import { ENTITY_MODULE_PATH, ITEM_MODULE_PATH } from './item-page/item-page-routing-paths'; -import { HOME_PAGE_PATH } from './app-routing-paths'; -import { DsoOptionMenu } from './shared/menu/providers/dso-option-menu.service'; +import { DsoOptionMenu } from './shared/menu/providers/dso-option.menu'; export const MENUS = buildMenuStructure({ [MenuID.PUBLIC]: [ @@ -55,12 +54,11 @@ export const MENUS = buildMenuStructure({ ], [MenuID.DSO_EDIT]: [ DsoOptionMenu.withSubs([ + SubscribeMenuProvider.onRoute(COMMUNITY_MODULE_PATH, COLLECTION_MODULE_PATH), DSpaceObjectEditMenuProvider.onRoute(COMMUNITY_MODULE_PATH, COLLECTION_MODULE_PATH, ITEM_MODULE_PATH, ENTITY_MODULE_PATH), VersioningMenuProvider.onRoute(ITEM_MODULE_PATH, ENTITY_MODULE_PATH), OrcidMenuProvider.onRoute(ITEM_MODULE_PATH, ENTITY_MODULE_PATH), - ClaimMenuProvider.onRoute(ITEM_MODULE_PATH, ENTITY_MODULE_PATH), - // SubscribeMenuProvider, + ClaimMenuProvider.onRoute(ITEM_MODULE_PATH, ENTITY_MODULE_PATH, COLLECTION_MODULE_PATH), ]), - SubscribeMenuProvider.onRoute(COMMUNITY_MODULE_PATH, COLLECTION_MODULE_PATH), ], }); diff --git a/src/app/collection-page/collection-page-routing.module.ts b/src/app/collection-page/collection-page-routing.module.ts index d45b4b12a3..7930c1ae6f 100644 --- a/src/app/collection-page/collection-page-routing.module.ts +++ b/src/app/collection-page/collection-page-routing.module.ts @@ -5,9 +5,6 @@ import { CollectionBreadcrumbResolver } from '../core/breadcrumbs/collection-bre import { DSOBreadcrumbsService } from '../core/breadcrumbs/dso-breadcrumbs.service'; import { I18nBreadcrumbResolver } from '../core/breadcrumbs/i18n-breadcrumb.resolver'; import { LinkService } from '../core/cache/builders/link.service'; -import { SubscribeMenuProvider } from '../shared/menu/providers/comcol-subscribe.menu'; -import { DSpaceObjectEditMenuProvider } from '../shared/menu/providers/dso-edit.menu'; -import { StatisticsMenuProvider } from '../shared/menu/providers/statistics.menu'; import { CollectionPageAdministratorGuard } from './collection-page-administrator.guard'; import { COLLECTION_CREATE_PATH, diff --git a/src/app/init.service.ts b/src/app/init.service.ts index ce4055ebde..6dc99126ca 100644 --- a/src/app/init.service.ts +++ b/src/app/init.service.ts @@ -21,11 +21,10 @@ import { MetadataService } from './core/metadata/metadata.service'; import { BreadcrumbsService } from './breadcrumbs/breadcrumbs.service'; import { ThemeService } from './shared/theme-support/theme.service'; import { isAuthenticationBlocking } from './core/auth/selectors'; -import { distinctUntilChanged, find, switchMap } from 'rxjs/operators'; +import { distinctUntilChanged, find } from 'rxjs/operators'; import { Observable } from 'rxjs'; import { MenuService } from './shared/menu/menu.service'; import { MenuProviderService } from './shared/menu/menu-provider.service'; -import { MenuID } from './shared/menu/menu-id.model'; /** * Performs the initialization of the app. @@ -190,18 +189,9 @@ export abstract class InitService { this.metadata.listenForRouteChange(); this.breadcrumbsService.listenForRouteChanges(); this.themeService.listenForRouteChanges(); - // this.menuService.listenForRouteChanges(); this.menuProviderService.listenForRouteChanges(); } - protected initPersistentMenus(): void { - this.menuProviderService.resolvePersistentMenus().subscribe((done) => { - Object.values(MenuID).forEach((menuID) => { - this.menuService.buildRouteMenuSections(menuID); - }); - }); - } - /** * Emits once authentication is ready (no longer blocking) * @protected diff --git a/src/app/item-page/item-page-routing.module.ts b/src/app/item-page/item-page-routing.module.ts index 0a8438f543..1c93dab744 100644 --- a/src/app/item-page/item-page-routing.module.ts +++ b/src/app/item-page/item-page-routing.module.ts @@ -5,20 +5,13 @@ import { AuthenticatedGuard } from '../core/auth/authenticated.guard'; import { DSOBreadcrumbsService } from '../core/breadcrumbs/dso-breadcrumbs.service'; import { ItemBreadcrumbResolver } from '../core/breadcrumbs/item-breadcrumb.resolver'; import { LinkService } from '../core/cache/builders/link.service'; -import { DSpaceObjectEditMenuProvider } from '../shared/menu/providers/dso-edit.menu'; -import { ClaimMenuProvider } from '../shared/menu/providers/item-claim.menu'; -import { OrcidMenuProvider } from '../shared/menu/providers/item-orcid.menu'; -import { VersioningMenuProvider } from '../shared/menu/providers/item-versioning.menu'; -import { StatisticsMenuProvider } from '../shared/menu/providers/statistics.menu'; -import { BitstreamRequestACopyPageComponent } from './bitstreams/request-a-copy/bitstream-request-a-copy-page.component'; +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 { ItemPageAdministratorGuard } from './item-page-administrator.guard'; -import { - ITEM_EDIT_PATH, - ORCID_PATH, - UPLOAD_BITSTREAM_PATH, -} from './item-page-routing-paths'; +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'; diff --git a/src/app/navbar/expandable-navbar-section/expandable-navbar-section.component.html b/src/app/navbar/expandable-navbar-section/expandable-navbar-section.component.html index 61ca511674..dd80a992b7 100644 --- a/src/app/navbar/expandable-navbar-section/expandable-navbar-section.component.html +++ b/src/app/navbar/expandable-navbar-section/expandable-navbar-section.component.html @@ -1,4 +1,4 @@ -
+ -
+ diff --git a/src/app/shared/dso-page/dso-edit-menu/dso-edit-expandable-menu-section/dso-edit-menu-expandable-section.component.ts b/src/app/shared/dso-page/dso-edit-menu/dso-edit-expandable-menu-section/dso-edit-menu-expandable-section.component.ts index f3a2fd627a..a381ad9f66 100644 --- a/src/app/shared/dso-page/dso-edit-menu/dso-edit-expandable-menu-section/dso-edit-menu-expandable-section.component.ts +++ b/src/app/shared/dso-page/dso-edit-menu/dso-edit-expandable-menu-section/dso-edit-menu-expandable-section.component.ts @@ -65,6 +65,6 @@ export class DsoEditMenuExpandableSectionComponent extends AbstractMenuSectionCo this.hasSubSections$ = this.subSections$.pipe( map((subSections) => isNotEmpty(subSections)) ); - + } } diff --git a/src/app/shared/dso-page/dso-edit-menu/dso-edit-menu-section/dso-edit-menu-section.component.spec.ts b/src/app/shared/dso-page/dso-edit-menu/dso-edit-menu-section/dso-edit-menu-section.component.spec.ts index f0815c5415..bfd881aea2 100644 --- a/src/app/shared/dso-page/dso-edit-menu/dso-edit-menu-section/dso-edit-menu-section.component.spec.ts +++ b/src/app/shared/dso-page/dso-edit-menu/dso-edit-menu-section/dso-edit-menu-section.component.spec.ts @@ -127,17 +127,17 @@ describe('DsoEditMenuSectionComponent', () => { stopPropagation: jasmine.createSpy('stopPropagation'), }); it('should call the item model function when not disabled', () => { - spyOn(component.section.model as OnClickMenuItemModel, 'function'); + spyOn((component as any).section.model as OnClickMenuItemModel, 'function'); component.activate(mockEvent); - expect((component.section.model as OnClickMenuItemModel).function).toHaveBeenCalled(); + expect(((component as any).section.model as OnClickMenuItemModel).function).toHaveBeenCalled(); }); it('should call not the item model function when disabled', () => { - spyOn(component.section.model as OnClickMenuItemModel, 'function'); + spyOn((component as any).section.model as OnClickMenuItemModel, 'function'); component.itemModel.disabled = true; component.activate(mockEvent); - expect((component.section.model as OnClickMenuItemModel).function).not.toHaveBeenCalled(); + expect(((component as any).section.model as OnClickMenuItemModel).function).not.toHaveBeenCalled(); component.itemModel.disabled = false; }); }); diff --git a/src/app/shared/dso-page/dso-edit-menu/dso-edit-menu.component.spec.ts b/src/app/shared/dso-page/dso-edit-menu/dso-edit-menu.component.spec.ts index 5616e8ea10..d7f0ead878 100644 --- a/src/app/shared/dso-page/dso-edit-menu/dso-edit-menu.component.spec.ts +++ b/src/app/shared/dso-page/dso-edit-menu/dso-edit-menu.component.spec.ts @@ -16,6 +16,7 @@ import { getMockThemeService } from '../../mocks/theme-service.mock'; import { DsoPageModule } from '../dso-page.module'; +import { TextMenuItemModel } from '../../menu/menu-item/models/text.model'; describe('DsoEditMenuComponent', () => { let comp: DsoEditMenuComponent; @@ -32,9 +33,10 @@ describe('DsoEditMenuComponent', () => { active: false, visible: true, model: { + text: 'section-text', type: null, disabled: false, - } as MenuItemModel, + } as TextMenuItemModel, icon: 'pencil-alt', index: 1 }; diff --git a/src/app/shared/menu/menu-provider.service.ts b/src/app/shared/menu/menu-provider.service.ts index b749c9f5a0..a984e594f8 100644 --- a/src/app/shared/menu/menu-provider.service.ts +++ b/src/app/shared/menu/menu-provider.service.ts @@ -66,12 +66,8 @@ export class MenuProviderService { } - public resolvePersistentMenus( - // route: ActivatedRouteSnapshot, - // state: RouterStateSnapshot - ): Observable { - - return combineLatest([ + public initPersistentMenus() { + combineLatest([ ...this.providers .map((provider) => { return provider; @@ -98,63 +94,69 @@ export class MenuProviderService { return [waitForMenus]; }), map(done => done.every(Boolean)), - ); + ).subscribe((done) => { + Object.values(MenuID).forEach((menuID) => { + this.menuService.buildRouteMenuSections(menuID); + }); + }); } public resolveRouteMenus( route: ActivatedRouteSnapshot, state: RouterStateSnapshot ): Observable { - return combineLatest([ + const currentNonPersistentMenuSections$ = combineLatest([ ...Object.values(MenuID).map((menuID) => { return this.menuService.getNonPersistentMenuSections(menuID).pipe( take(1), map((sections) => { return {menuId: menuID, sections: sections}; })); - })]) - .pipe( - switchMap((menuSectionsPerMenu) => { - this.removeNonPersistentSections(menuSectionsPerMenu); - return combineLatest([ - ...this.providers - .filter(provider => { - let shouldUpdate = false; - if (!provider.shouldPersistOnRouteChange && isNotEmpty(provider.activePaths)) { - provider.activePaths.forEach((path) => { - if (state.url.includes(path)) { - shouldUpdate = true; - } - }); - } else if (!provider.shouldPersistOnRouteChange) { - shouldUpdate = true; - } - return shouldUpdate; - }) - .map(provider => provider.getSections(route, state) - .pipe( - map((sections) => { - return {provider: provider, sections: sections}; - }), - ) - ) - ]); - }), - switchMap((providerWithSections: { provider: AbstractMenuProvider, sections: PartialMenuSection[] }[]) => { - const waitForMenus = providerWithSections.map((providerWithSection: { - provider: AbstractMenuProvider, - sections: PartialMenuSection[] - }, sectionIndex) => { - providerWithSection.sections.forEach((section) => { - this.addSection(providerWithSection, section); + })]); + + const routeDependentMenuSections$ = combineLatest([ + ...this.providers + .filter(provider => { + let shouldUpdate = false; + if (!provider.shouldPersistOnRouteChange && isNotEmpty(provider.activePaths)) { + provider.activePaths.forEach((path) => { + if (state.url.includes(path)) { + shouldUpdate = true; + } }); - return this.waitForMenu$(providerWithSection.provider.menuID); + } else if (!provider.shouldPersistOnRouteChange) { + shouldUpdate = true; + } + return shouldUpdate; + }) + .map(provider => provider.getSections(route, state) + .pipe( + map((sections) => { + return {provider: provider, sections: sections}; + }), + ) + ) + ]); + + return combineLatest([ + currentNonPersistentMenuSections$, + routeDependentMenuSections$ + ]).pipe( + switchMap(([currentMenusWithSections, providerWithSections]) => { + this.removeNonPersistentSections(currentMenusWithSections); + const waitForMenus = providerWithSections.map((providerWithSection: { + provider: AbstractMenuProvider, + sections: PartialMenuSection[] + }) => { + providerWithSection.sections.forEach((section) => { + this.addSection(providerWithSection, section); }); - return [waitForMenus]; - }), - map(done => done.every(Boolean)), - ); - // } + return this.waitForMenu$(providerWithSection.provider.menuID); + }); + return [waitForMenus]; + }), + map(done => done.every(Boolean)), + ); } private addSection(providerWithSection: { @@ -167,7 +169,7 @@ export class MenuProviderService { parentID: section.parentID ?? providerWithSection.provider.parentID, index: section.index ?? providerWithSection.provider.index, shouldPersistOnRouteChange: section.shouldPersistOnRouteChange ?? providerWithSection.provider.shouldPersistOnRouteChange, - isExpandable: section.isExpandable ?? providerWithSection.provider.isExpandable, + alwaysRenderExpandable: section.alwaysRenderExpandable ?? providerWithSection.provider.alwaysRenderExpandable, }); } diff --git a/src/app/shared/menu/menu-provider.ts b/src/app/shared/menu/menu-provider.ts index f13d323744..7044870f17 100644 --- a/src/app/shared/menu/menu-provider.ts +++ b/src/app/shared/menu/menu-provider.ts @@ -5,28 +5,15 @@ * * http://www.dspace.org/license/ */ - -import { - ActivatedRouteSnapshot, - RouterStateSnapshot, -} from '@angular/router'; -import { Omit } from '@material-ui/core'; +/* eslint-disable max-classes-per-file */ +import { ActivatedRouteSnapshot, RouterStateSnapshot, } from '@angular/router'; import flatten from 'lodash/flatten'; -import { - combineLatest, - Observable, -} from 'rxjs'; +import { combineLatest, Observable, } from 'rxjs'; import { map } from 'rxjs/operators'; import { MenuID } from './menu-id.model'; -import { MenuItemModels, MenuSection } from './menu-section.model'; -import { APP_INITIALIZER, Provider, Type } from '@angular/core'; -import { APP_CONFIG } from '../../../config/app-config.interface'; -import { TransferState } from '@angular/platform-browser'; -import { environment } from '../../../environments/environment'; -import { HOME_PAGE_PATH } from '../../app-routing-paths'; -import { MENU_PROVIDER } from './menu.structure'; +import { MenuItemModels } from './menu-section.model'; +import { Type } from '@angular/core'; -// export type PartialMenuSection = Omit; export interface PartialMenuSection { id?: string; visible: boolean; @@ -36,11 +23,10 @@ export interface PartialMenuSection { active?: boolean; shouldPersistOnRouteChange?: boolean; icon?: string; - isExpandable?: boolean; + alwaysRenderExpandable?: boolean; } - export interface MenuProvider { shouldPersistOnRouteChange?: boolean, menuID?: MenuID; @@ -49,35 +35,37 @@ export interface MenuProvider { getSections(route?: ActivatedRouteSnapshot, state?: RouterStateSnapshot): Observable; } -export class MenuProviderTypeWithPaths { +export class MenuProviderTypeWithOptions { providerType: Type; - paths: string[]; -} + paths?: string[]; + childProviderTypes?: (Type | MenuProviderTypeWithOptions)[]; -export class MenuProviderTypeWithSubs { - providerType: Type; - childProviderTypes: (Type | MenuProviderTypeWithPaths)[]; } export abstract class AbstractMenuProvider implements MenuProvider { - shouldPersistOnRouteChange = true; + + /** + * ID of the menu this provider is part of + * If not set up, this will be set based on the provider class name + */ menuID?: MenuID; + + /** + * Whether the sections of this menu should be set on the + */ + shouldPersistOnRouteChange = true; menuProviderId?: string; index?: number; activePaths?: string[]; parentID?: string; - isExpandable = false; + + /** + * Whether the menu section or top section of this provider will always be rendered as expandable and hidden when no children are present + */ + alwaysRenderExpandable? = false; - abstract getSections(route?: ActivatedRouteSnapshot, state?: RouterStateSnapshot): Observable; - - protected concat(...sections$: Observable[]): Observable { - return combineLatest(sections$).pipe( - map(sections => flatten(sections)), - ); - } - - public static onRoute(...paths: string[]) { + public static onRoute(...paths: string[]): MenuProviderTypeWithOptions { if (!AbstractMenuProvider.isPrototypeOf(this)) { throw new Error( 'onRoute should only be called from concrete subclasses of AbstractMenuProvider' @@ -87,6 +75,29 @@ export abstract class AbstractMenuProvider implements MenuProvider { const providerType = this as unknown as Type; return {providerType: providerType, paths: paths}; } + + /** + * Method to add sub menu providers to this top provider + * @param childProviders - the list of sub providers that will provide subsections for this provider + */ + public static withSubs(childProviders: (Type | MenuProviderTypeWithOptions)[]): MenuProviderTypeWithOptions { + if (!AbstractMenuProvider.isPrototypeOf(this)) { + throw new Error( + 'withSubs should only be called from concrete subclasses of AbstractMenuProvider' + ); + } + + const providerType = this as unknown as Type; + return {providerType: providerType, childProviderTypes: childProviders}; + } + + abstract getSections(route?: ActivatedRouteSnapshot, state?: RouterStateSnapshot): Observable; + + protected concat(...sections$: Observable[]): Observable { + return combineLatest(sections$).pipe( + map(sections => flatten(sections)), + ); + } } diff --git a/src/app/shared/menu/menu-section.model.ts b/src/app/shared/menu/menu-section.model.ts index 5c53364889..700f179a3f 100644 --- a/src/app/shared/menu/menu-section.model.ts +++ b/src/app/shared/menu/menu-section.model.ts @@ -80,5 +80,5 @@ export interface MenuSection { */ icon?: string; - isExpandable?: boolean; + alwaysRenderExpandable?: boolean; } diff --git a/src/app/shared/menu/menu-section/menu-section.component.spec.ts b/src/app/shared/menu/menu-section/menu-section.component.spec.ts index 8949c6c1b0..9f2deae0e9 100644 --- a/src/app/shared/menu/menu-section/menu-section.component.spec.ts +++ b/src/app/shared/menu/menu-section/menu-section.component.spec.ts @@ -1,6 +1,5 @@ import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; import { NoopAnimationsModule } from '@angular/platform-browser/animations'; -import { Router } from '@angular/router'; import { TranslateModule } from '@ngx-translate/core'; import { ChangeDetectionStrategy, diff --git a/src/app/shared/menu/menu.component.spec.ts b/src/app/shared/menu/menu.component.spec.ts index f0660fab4a..707b1c0746 100644 --- a/src/app/shared/menu/menu.component.spec.ts +++ b/src/app/shared/menu/menu.component.spec.ts @@ -15,6 +15,8 @@ import { AuthorizationDataService } from '../../core/data/feature-authorization/ import { createSuccessfulRemoteDataObject } from '../remote-data.utils'; import { ThemeService } from '../theme-support/theme.service'; import { getMockThemeService } from '../mocks/theme-service.mock'; +import { MenuItemType } from './menu-item-type.model'; +import { TextMenuItemModel } from './menu-item/models/text.model'; describe('MenuComponent', () => { let comp: MenuComponent; @@ -22,6 +24,16 @@ describe('MenuComponent', () => { let menuService: MenuService; let router: any; + const menuSection: MenuSection = { + id: 'browse', + model: { + type: MenuItemType.TEXT, + text: 'menu.section.browse_global', + } as TextMenuItemModel, + icon: 'globe', + visible: true, + } + const mockMenuID = 'mock-menuID' as MenuID; const mockStatisticSection = { 'id': 'statistics_site', 'active': true, 'visible': true, 'index': 2, 'type': 'statistics', 'model': { 'type': 1, 'text': 'menu.section.statistics', 'link': 'statistics' } }; @@ -76,7 +88,7 @@ describe('MenuComponent', () => { comp.menuID = mockMenuID; menuService = (comp as any).menuService; router = TestBed.inject(Router); - spyOn(comp as any, 'getSectionDataInjector').and.returnValue(MenuSection); + spyOn(comp as any, 'getSectionDataInjector').and.returnValue(menuSection); spyOn(comp as any, 'getSectionComponent').and.returnValue(observableOf({})); fixture.detectChanges(); }); diff --git a/src/app/shared/menu/menu.component.ts b/src/app/shared/menu/menu.component.ts index f3b04a26b7..89f6fb3e73 100644 --- a/src/app/shared/menu/menu.component.ts +++ b/src/app/shared/menu/menu.component.ts @@ -218,7 +218,7 @@ export class MenuComponent implements OnInit, OnDestroy { private getSectionComponent(section: MenuSection): Observable> { return this.menuService.hasSubSections(this.menuID, section.id).pipe( map((expandable: boolean) => { - return getComponentForMenu(this.menuID, expandable || section.isExpandable, this.themeService.getThemeName()); + return getComponentForMenu(this.menuID, expandable || section.alwaysRenderExpandable, this.themeService.getThemeName()); } ), ); diff --git a/src/app/shared/menu/menu.resolver.ts b/src/app/shared/menu/menu.resolver.ts index 15e0dc9803..9f01934670 100644 --- a/src/app/shared/menu/menu.resolver.ts +++ b/src/app/shared/menu/menu.resolver.ts @@ -5,17 +5,10 @@ * * http://www.dspace.org/license/ */ -import { - inject, - Type, -} from '@angular/core'; -import { - ActivatedRouteSnapshot, - RouterStateSnapshot, -} from '@angular/router'; -import { Observable } from 'rxjs'; -import { AbstractMenuProvider } from './menu-provider'; -import { MenuProviderService } from './menu-provider.service'; + + + + // export function resolveStaticMenus(): (ActivatedRouteSnapshot, RouterStateSnapshot, ProviderMenuService) => Observable { // return ( diff --git a/src/app/shared/menu/menu.service.ts b/src/app/shared/menu/menu.service.ts index df6b246ec3..ed367a1fc3 100644 --- a/src/app/shared/menu/menu.service.ts +++ b/src/app/shared/menu/menu.service.ts @@ -2,7 +2,7 @@ import { Injectable } from '@angular/core'; import { createSelector, MemoizedSelector, select, Store } from '@ngrx/store'; import { AppState, keySelector } from '../../app.reducer'; import { combineLatest as observableCombineLatest, Observable } from 'rxjs'; -import { filter, map, switchMap, take, tap } from 'rxjs/operators'; +import { filter, map, switchMap, take } from 'rxjs/operators'; import { ActivateMenuSectionAction, AddMenuSectionAction, diff --git a/src/app/shared/menu/menu.structure.ts b/src/app/shared/menu/menu.structure.ts index 9eaca8d400..bc58f70391 100644 --- a/src/app/shared/menu/menu.structure.ts +++ b/src/app/shared/menu/menu.structure.ts @@ -5,48 +5,22 @@ * * http://www.dspace.org/license/ */ -import { - InjectionToken, - Provider, - Type, -} from '@angular/core'; +import { InjectionToken, Provider, Type, } from '@angular/core'; import { MenuID } from './menu-id.model'; -import { AbstractMenuProvider } from './menu-provider'; +import { AbstractMenuProvider, MenuProviderTypeWithOptions } from './menu-provider'; import { MenuProviderService } from './menu-provider.service'; import { hasValue, isNotEmpty } from '../empty.util'; export const MENU_PROVIDER = new InjectionToken('MENU_PROVIDER'); type MenuStructure = { - [key in MenuID]: (Type | {providerType: Type, paths: string[]} | {providerType: Type, childProviderTypes: any[]})[]; + [key in MenuID]: (Type | MenuProviderTypeWithOptions)[]; }; -function resolveProvider(providerType: Type , menuID: string, index: number, paths?: string[], parentID?: string, childProviders? : Type[]) { - return { - provide: MENU_PROVIDER, - multi: true, - useFactory(provider: AbstractMenuProvider): AbstractMenuProvider { - provider.menuID = menuID as MenuID; - provider.index = provider.index ?? index; - if (hasValue(parentID)) { - provider.menuProviderId = `${parentID}_${provider.constructor.name}-${provider.index}` - provider.parentID = parentID; - } else { - provider.menuProviderId = `${provider.constructor.name}-${provider.index}`; - } - if (isNotEmpty(paths)) { - provider.activePaths = paths; - provider.shouldPersistOnRouteChange = false; - } - if (isNotEmpty(childProviders)) { - provider.shouldPersistOnRouteChange = false; - } - return provider; - }, - deps: [providerType], - }; -} - +/** + * Builds the menu structure by converting the provider types into resolved providers + * @param structure - The app menus structure + */ export function buildMenuStructure(structure: MenuStructure): Provider[] { const providers: Provider[] = [ MenuProviderService, @@ -54,44 +28,77 @@ export function buildMenuStructure(structure: MenuStructure): Provider[] { Object.entries(structure).forEach(([menuID, providerTypes]) => { for (const [index, providerType] of providerTypes.entries()) { - // todo: should complain if not injectable! - - if (providerType.hasOwnProperty('providerType') && providerType.hasOwnProperty('paths')) { - const providerPart = (providerType as any).providerType; - const paths = (providerType as any).paths; - providers.push(providerPart); - providers.push(resolveProvider(providerPart, menuID, index, paths)); - } else if (providerType.hasOwnProperty('providerType') && providerType.hasOwnProperty('childProviderTypes')){ - const providerPart = (providerType as any).providerType; - - const childProviderList = []; - const childProviderTypes = (providerType as any).childProviderTypes; - childProviderTypes.forEach((childProviderType, childIndex: number) => { - - - if (childProviderType.hasOwnProperty('providerType') && childProviderType.hasOwnProperty('paths')) { - const childProviderTypePart = (childProviderType as any).providerType; - const paths = (childProviderType as any).paths; - providers.push(childProviderTypePart); - providers.push(resolveProvider(childProviderTypePart, menuID, childIndex, paths, `${providerPart.name}-${index}`)) - childProviderList.push(childProviderTypePart); - } else { - providers.push(childProviderType) - providers.push(resolveProvider(childProviderType, menuID, childIndex, undefined, `${providerPart.name}-${index}`)) - childProviderList.push(childProviderType); - } - }) - - providers.push(providerPart); - providers.push(resolveProvider(providerPart, menuID, index, undefined, undefined, childProviderList)); - - - } else { - providers.push(providerType as Type ); - providers.push(resolveProvider(providerType as Type, menuID, index)); - } + processProviderType(providers, menuID, providerType, index); } }); return providers; } + +/** + * Process a single provider type and add it to the list of providers + * When the provider type contains paths, the paths will be added to resolved provider + * When the provider type has sub provider, the sub providers will be processed with the current provider type as parent + * @param providers - The list of providers + * @param providerType - The provider to resolve and add to the list + * @param menuID - The ID of the menu to which the provider belongs + * @param index - The index of the provider + * @param parentID - The ID of the parent provider if relevant + * @param hasSubProviders - Whether this provider has sub providers + */ +function processProviderType(providers: Provider[], menuID: string, providerType: Type | MenuProviderTypeWithOptions, index: number, parentID?: string, hasSubProviders?: boolean) { + if (providerType.hasOwnProperty('providerType') && providerType.hasOwnProperty('childProviderTypes')) { + const providerPart = (providerType as any).providerType; + const childProviderTypes = (providerType as any).childProviderTypes; + + childProviderTypes.forEach((childProviderType, childIndex: number) => { + processProviderType(providers, menuID, childProviderType, childIndex, `${providerPart.name}`, hasSubProviders); + }); + processProviderType(providers, menuID, providerPart, index, parentID, true); + + } else if (providerType.hasOwnProperty('providerType') && providerType.hasOwnProperty('paths')) { + const providerPart = (providerType as any).providerType; + const paths = (providerType as any).paths; + addProviderToList(providers, providerPart, menuID, index, parentID, hasSubProviders, paths); + } else { + addProviderToList(providers, providerType as Type, menuID, index, parentID, hasSubProviders); + } +} + +/** + * Resolves and adds a provider to a list of providers + * @param providers - The list of providers + * @param providerType - The provider to resolve and add to the list + * @param menuID - The ID of the menu to which the provider belongs + * @param index - The index of the provider + * @param parentID - The ID of the parent provider if relevant + * @param hasSubProviders - Whether this provider has sub providers + * @param paths - The paths this provider should be active on if relevant + */ +function addProviderToList(providers: Provider[], providerType: Type, menuID: string, index: number, parentID?: string, hasSubProviders?: boolean, paths?: string[]) { + const resolvedProvider = { + provide: MENU_PROVIDER, + multi: true, + useFactory(provider: AbstractMenuProvider): AbstractMenuProvider { + provider.menuID = menuID as MenuID; + provider.index = provider.index ?? index; + if (hasValue(parentID)) { + provider.menuProviderId = `${parentID}_${provider.constructor.name}`; + provider.parentID = parentID; + } else { + provider.menuProviderId = `${provider.constructor.name}`; + } + if (isNotEmpty(paths)) { + provider.activePaths = paths; + provider.shouldPersistOnRouteChange = false; + } + if (hasSubProviders) { + provider.shouldPersistOnRouteChange = false; + } + return provider; + }, + deps: [providerType], + }; + providers.push(resolvedProvider); + providers.push(providerType); +} diff --git a/src/app/shared/menu/providers/access-control.menu.ts b/src/app/shared/menu/providers/access-control.menu.ts index 311fc49777..312ad2d065 100644 --- a/src/app/shared/menu/providers/access-control.menu.ts +++ b/src/app/shared/menu/providers/access-control.menu.ts @@ -8,24 +8,17 @@ import { Injectable } from '@angular/core'; import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; -import { - combineLatest as observableCombineLatest, - map, - Observable, - of as observableOf, -} from 'rxjs'; +import { combineLatest as observableCombineLatest, map, Observable, of as observableOf, } from 'rxjs'; import { AuthorizationDataService } from '../../../core/data/feature-authorization/authorization-data.service'; import { FeatureID } from '../../../core/data/feature-authorization/feature-id'; import { ScriptDataService } from '../../../core/data/processes/script-data.service'; import { MenuItemType } from '../menu-item-type.model'; -import { - AbstractExpandableMenuProvider, - MenuSubSection, - MenuTopSection, -} from './expandable-menu-provider'; +import { AbstractExpandableMenuProvider, } from './helper-providers/expandable-menu-provider'; +import { PartialMenuSection } from '../menu-provider'; @Injectable() export class AccessControlMenuProvider extends AbstractExpandableMenuProvider { + constructor( protected authorizationService: AuthorizationDataService, protected scriptDataService: ScriptDataService, @@ -34,17 +27,18 @@ export class AccessControlMenuProvider extends AbstractExpandableMenuProvider { super(); } - public getTopSection(): Observable { + public getTopSection(): Observable { return observableOf({ - model: { - type: MenuItemType.TEXT, - text: 'menu.section.access_control', - }, - icon: 'key' + model: { + type: MenuItemType.TEXT, + text: 'menu.section.access_control', + }, + icon: 'key', + visible: true, }); } - public getSubSections(): Observable { + public getSubSections(): Observable { return observableCombineLatest([ this.authorizationService.isAuthorized(FeatureID.AdministratorOf), this.authorizationService.isAuthorized(FeatureID.CanManageGroups), @@ -87,7 +81,7 @@ export class AccessControlMenuProvider extends AbstractExpandableMenuProvider { // link: '' // } as LinkMenuItemModel, // }, - ] as MenuSubSection[]; + ]; }), ); } diff --git a/src/app/shared/menu/providers/admin-search.menu.ts b/src/app/shared/menu/providers/admin-search.menu.ts index 9d5e2cdb98..dbc288b4c6 100644 --- a/src/app/shared/menu/providers/admin-search.menu.ts +++ b/src/app/shared/menu/providers/admin-search.menu.ts @@ -7,18 +7,11 @@ */ import { Injectable } from '@angular/core'; -import { - combineLatest, - map, - Observable, -} from 'rxjs'; +import { combineLatest, map, Observable, } from 'rxjs'; import { AuthorizationDataService } from '../../../core/data/feature-authorization/authorization-data.service'; import { FeatureID } from '../../../core/data/feature-authorization/feature-id'; import { MenuItemType } from '../menu-item-type.model'; -import { - AbstractMenuProvider, - PartialMenuSection, -} from '../menu-provider'; +import { AbstractMenuProvider, PartialMenuSection, } from '../menu-provider'; @Injectable() export class AdminSearchMenuProvider extends AbstractMenuProvider { diff --git a/src/app/shared/menu/providers/browse.menu.ts b/src/app/shared/menu/providers/browse.menu.ts index 06a6cf3dbe..e6ca1f3db5 100644 --- a/src/app/shared/menu/providers/browse.menu.ts +++ b/src/app/shared/menu/providers/browse.menu.ts @@ -7,10 +7,7 @@ */ import { Injectable } from '@angular/core'; -import { - Observable, - of as observableOf, -} from 'rxjs'; +import { Observable, of as observableOf, } from 'rxjs'; import { map } from 'rxjs/operators'; import { BrowseService } from '../../../core/browse/browse.service'; import { PaginatedList } from '../../../core/data/paginated-list.model'; @@ -19,11 +16,8 @@ import { BrowseDefinition } from '../../../core/shared/browse-definition.model'; import { getFirstSucceededRemoteData } from '../../../core/shared/operators'; import { MenuItemType } from '../menu-item-type.model'; import { TextMenuItemModel } from '../menu-item/models/text.model'; -import { - AbstractExpandableMenuProvider, - MenuSubSection, - MenuTopSection, -} from './expandable-menu-provider'; +import { AbstractExpandableMenuProvider, } from './helper-providers/expandable-menu-provider'; +import { PartialMenuSection } from '../menu-provider'; @Injectable() export class BrowseMenuProvider extends AbstractExpandableMenuProvider { @@ -33,7 +27,7 @@ export class BrowseMenuProvider extends AbstractExpandableMenuProvider { super(); } - getTopSection(): Observable { + getTopSection(): Observable { return observableOf( { model: { @@ -41,11 +35,12 @@ export class BrowseMenuProvider extends AbstractExpandableMenuProvider { text: 'menu.section.browse_global', } as TextMenuItemModel, icon: 'globe', + visible: true, }, ); } - getSubSections(): Observable { + getSubSections(): Observable { return this.browseService.getBrowseDefinitions().pipe( getFirstSucceededRemoteData(), map((rd: RemoteData>) => { @@ -60,7 +55,7 @@ export class BrowseMenuProvider extends AbstractExpandableMenuProvider { }, }; }), - ] as MenuSubSection[]; + ]; }), ); } diff --git a/src/app/shared/menu/providers/comcol-subscribe.menu.ts b/src/app/shared/menu/providers/comcol-subscribe.menu.ts index 77a4b86cfe..9c17aa3952 100644 --- a/src/app/shared/menu/providers/comcol-subscribe.menu.ts +++ b/src/app/shared/menu/providers/comcol-subscribe.menu.ts @@ -7,40 +7,27 @@ */ import { Injectable } from '@angular/core'; import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; -import { - combineLatest, - Observable, -} from 'rxjs'; +import { combineLatest, Observable, } from 'rxjs'; import { map } from 'rxjs/operators'; -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 { Collection } from '../../../core/shared/collection.model'; -import { COLLECTION } from '../../../core/shared/collection.resource-type'; -import { Community } from '../../../core/shared/community.model'; -import { COMMUNITY } from '../../../core/shared/community.resource-type'; import { SubscriptionModalComponent } from '../../subscriptions/subscription-modal/subscription-modal.component'; import { MenuItemType } from '../menu-item-type.model'; import { OnClickMenuItemModel } from '../menu-item/models/onclick.model'; import { PartialMenuSection } from '../menu-provider'; -import { DSpaceObjectPageMenuProvider } from './dso.menu'; +import { DSpaceObjectPageMenuProvider } from './helper-providers/dso.menu'; +import { DSpaceObject } from '../../../core/shared/dspace-object.model'; @Injectable() -export class SubscribeMenuProvider extends DSpaceObjectPageMenuProvider { +export class SubscribeMenuProvider extends DSpaceObjectPageMenuProvider { constructor( protected authorizationService: AuthorizationDataService, protected modalService: NgbModal, - protected dsoDataService: DSpaceObjectDataService, ) { - super(dsoDataService); + super(); } - // protected isApplicable(dso: Community | Collection): boolean { - // // @ts-ignore - // return dso?.type === COMMUNITY.value || dso?.type === COLLECTION.value; - // } - - public getSectionsForContext(dso: Community | Collection): Observable { + public getSectionsForContext(dso: DSpaceObject): Observable { return combineLatest([ this.authorizationService.isAuthorized(FeatureID.CanSubscribe, dso.self), ]).pipe( diff --git a/src/app/shared/menu/providers/community-list.menu.ts b/src/app/shared/menu/providers/community-list.menu.ts index 5637db8ff7..028d5618af 100644 --- a/src/app/shared/menu/providers/community-list.menu.ts +++ b/src/app/shared/menu/providers/community-list.menu.ts @@ -7,15 +7,9 @@ */ import { Injectable } from '@angular/core'; -import { - Observable, - of, -} from 'rxjs'; +import { Observable, of, } from 'rxjs'; import { MenuItemType } from '../menu-item-type.model'; -import { - AbstractMenuProvider, - PartialMenuSection, -} from '../menu-provider'; +import { AbstractMenuProvider, PartialMenuSection, } from '../menu-provider'; @Injectable() export class CommunityListMenuProvider extends AbstractMenuProvider { diff --git a/src/app/shared/menu/providers/curation.menu.ts b/src/app/shared/menu/providers/curation.menu.ts index 16b4501048..1c35e3fcd8 100644 --- a/src/app/shared/menu/providers/curation.menu.ts +++ b/src/app/shared/menu/providers/curation.menu.ts @@ -7,19 +7,12 @@ */ import { Injectable } from '@angular/core'; -import { - combineLatest, - map, - Observable, -} from 'rxjs'; +import { combineLatest, map, Observable, } from 'rxjs'; import { AuthorizationDataService } from '../../../core/data/feature-authorization/authorization-data.service'; import { FeatureID } from '../../../core/data/feature-authorization/feature-id'; import { MenuItemType } from '../menu-item-type.model'; import { LinkMenuItemModel } from '../menu-item/models/link.model'; -import { - AbstractMenuProvider, - PartialMenuSection, -} from '../menu-provider'; +import { AbstractMenuProvider, PartialMenuSection, } from '../menu-provider'; @Injectable() export class CurationMenuProvider extends AbstractMenuProvider { diff --git a/src/app/shared/menu/providers/dso-edit.menu.ts b/src/app/shared/menu/providers/dso-edit.menu.ts index 35eccf7e24..5d5a480a92 100644 --- a/src/app/shared/menu/providers/dso-edit.menu.ts +++ b/src/app/shared/menu/providers/dso-edit.menu.ts @@ -6,13 +6,9 @@ * http://www.dspace.org/license/ */ import { Injectable } from '@angular/core'; -import { - combineLatest, - Observable, -} from 'rxjs'; +import { combineLatest, Observable, } from 'rxjs'; import { map } 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 { DSpaceObject } from '../../../core/shared/dspace-object.model'; @@ -20,15 +16,14 @@ import { URLCombiner } from '../../../core/url-combiner/url-combiner'; import { MenuItemType } from '../menu-item-type.model'; import { LinkMenuItemModel } from '../menu-item/models/link.model'; import { PartialMenuSection } from '../menu-provider'; -import { DSpaceObjectPageMenuProvider } from './dso.menu'; +import { DSpaceObjectPageMenuProvider } from './helper-providers/dso.menu'; @Injectable() -export class DSpaceObjectEditMenuProvider extends DSpaceObjectPageMenuProvider { +export class DSpaceObjectEditMenuProvider extends DSpaceObjectPageMenuProvider { constructor( protected authorizationDataService: AuthorizationDataService, - protected dsoDataService: DSpaceObjectDataService, ) { - super(dsoDataService); + super(); } public getSectionsForContext(dso: DSpaceObject): Observable { diff --git a/src/app/shared/menu/providers/dso-option-menu.service.ts b/src/app/shared/menu/providers/dso-option.menu.ts similarity index 51% rename from src/app/shared/menu/providers/dso-option-menu.service.ts rename to src/app/shared/menu/providers/dso-option.menu.ts index 5cf657bca4..4c5ab9c625 100644 --- a/src/app/shared/menu/providers/dso-option-menu.service.ts +++ b/src/app/shared/menu/providers/dso-option.menu.ts @@ -9,21 +9,29 @@ import { Injectable } from '@angular/core'; import { Observable, of, } from 'rxjs'; import { MenuItemType } from '../menu-item-type.model'; -import { AbstractExpandableParentMenuProvider } from './expandable-parent-menu-provider'; import { PartialMenuSection } from '../menu-provider'; +import { DSpaceObjectPageMenuProvider } from './helper-providers/dso.menu'; +import { DSpaceObject } from 'src/app/core/shared/dspace-object.model'; +import { hasValue } from '../../empty.util'; @Injectable() -export class DsoOptionMenu extends AbstractExpandableParentMenuProvider { +export class DsoOptionMenu extends DSpaceObjectPageMenuProvider { - public getSections(): Observable { + alwaysRenderExpandable = true; + + protected isApplicable(dso: DSpaceObject): boolean { + return hasValue(dso); + } + + getSectionsForContext(dso: DSpaceObject): Observable { return of([ { visible: true, model: { type: MenuItemType.TEXT, - text: `menu.section.browse_global_communities_and_collections`, + text: this.getDsoType(dso) + '.page.options', }, - icon: 'diagram-project' + icon: 'ellipsis-vertical' }, ] as PartialMenuSection[]); } diff --git a/src/app/shared/menu/providers/dso.menu.ts b/src/app/shared/menu/providers/dso.menu.ts deleted file mode 100644 index 0545b7cd6f..0000000000 --- a/src/app/shared/menu/providers/dso.menu.ts +++ /dev/null @@ -1,56 +0,0 @@ -/** - * The contents of this file are subject to the license and copyright - * detailed in the LICENSE and NOTICE files at the root of the source - * tree and available online at - * - * http://www.dspace.org/license/ - */ -import { - ActivatedRouteSnapshot, - RouterStateSnapshot, -} from '@angular/router'; -import { Observable } from 'rxjs'; -import { map } from 'rxjs/operators'; -import { DSpaceObjectDataService } from '../../../core/data/dspace-object-data.service'; -import { DSpaceObject } from '../../../core/shared/dspace-object.model'; -import { getFirstCompletedRemoteData } from '../../../core/shared/operators'; -import { AbstractRouteContextMenuProvider } from './route-context.menu'; - -export abstract class DSpaceObjectPageMenuProvider extends AbstractRouteContextMenuProvider { - allRoutes = false; - - protected constructor( - protected dsoDataService: DSpaceObjectDataService, - ) { - super(); - } - - - public getRouteContext(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable { - // todo: would be cool to automatically switch to cached version of specific DSO - // ...but we can't really know which is which because the other resolver may run _after_ - // we could refactor the resolver to a function though; then it's less problematic to just call it here - return this.dsoDataService.findById(route.params.id, true, false).pipe( - getFirstCompletedRemoteData(), - map((dsoRD) => { - if (dsoRD.hasSucceeded) { - return dsoRD.payload as T; - } else { - return undefined; - } - }) - ); - } - - /** - * Retrieve the dso or entity type for an object to be used in generic messages - */ - protected getDsoType(dso: T) { - const renderType = dso.getRenderTypes()[0]; - if (typeof renderType === 'string' || renderType instanceof String) { - return renderType.toLowerCase(); - } else { - return dso.type.toString().toLowerCase(); - } - } -} diff --git a/src/app/shared/menu/providers/edit.menu.ts b/src/app/shared/menu/providers/edit.menu.ts index cc003aa259..d72b00ac15 100644 --- a/src/app/shared/menu/providers/edit.menu.ts +++ b/src/app/shared/menu/providers/edit.menu.ts @@ -8,23 +8,21 @@ import { Injectable } from '@angular/core'; import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; -import { - combineLatest, - map, - Observable, - of as observableOf, -} from 'rxjs'; +import { combineLatest, map, Observable, of as observableOf, } from 'rxjs'; import { AuthorizationDataService } from '../../../core/data/feature-authorization/authorization-data.service'; import { FeatureID } from '../../../core/data/feature-authorization/feature-id'; -import { ThemedEditCollectionSelectorComponent } from '../../dso-selector/modal-wrappers/edit-collection-selector/themed-edit-collection-selector.component'; -import { ThemedEditCommunitySelectorComponent } from '../../dso-selector/modal-wrappers/edit-community-selector/themed-edit-community-selector.component'; -import { ThemedEditItemSelectorComponent } from '../../dso-selector/modal-wrappers/edit-item-selector/themed-edit-item-selector.component'; -import { MenuItemType } from '../menu-item-type.model'; import { - AbstractExpandableMenuProvider, - MenuSubSection, - MenuTopSection, -} from './expandable-menu-provider'; + ThemedEditCollectionSelectorComponent +} from '../../dso-selector/modal-wrappers/edit-collection-selector/themed-edit-collection-selector.component'; +import { + ThemedEditCommunitySelectorComponent +} from '../../dso-selector/modal-wrappers/edit-community-selector/themed-edit-community-selector.component'; +import { + ThemedEditItemSelectorComponent +} from '../../dso-selector/modal-wrappers/edit-item-selector/themed-edit-item-selector.component'; +import { MenuItemType } from '../menu-item-type.model'; +import { AbstractExpandableMenuProvider, } from './helper-providers/expandable-menu-provider'; +import { PartialMenuSection } from '../menu-provider'; @Injectable() export class EditMenuProvider extends AbstractExpandableMenuProvider { @@ -35,7 +33,7 @@ export class EditMenuProvider extends AbstractExpandableMenuProvider { super(); } - public getTopSection(): Observable { + public getTopSection(): Observable { return observableOf( { model: { @@ -43,11 +41,12 @@ export class EditMenuProvider extends AbstractExpandableMenuProvider { text: 'menu.section.edit', }, icon: 'pencil', + visible: true, }, ); } - public getSubSections(): Observable { + public getSubSections(): Observable { return combineLatest([ this.authorizationService.isAuthorized(FeatureID.IsCollectionAdmin), this.authorizationService.isAuthorized(FeatureID.IsCommunityAdmin), @@ -85,7 +84,7 @@ export class EditMenuProvider extends AbstractExpandableMenuProvider { }, }, }, - ] as MenuSubSection[]; + ]; }), ); } diff --git a/src/app/shared/menu/providers/expandable-parent-menu-provider.ts b/src/app/shared/menu/providers/expandable-parent-menu-provider.ts deleted file mode 100644 index 0ee2735d56..0000000000 --- a/src/app/shared/menu/providers/expandable-parent-menu-provider.ts +++ /dev/null @@ -1,37 +0,0 @@ -/** - * The contents of this file are subject to the license and copyright - * detailed in the LICENSE and NOTICE files at the root of the source - * tree and available online at - * - * http://www.dspace.org/license/ - */ -import { combineLatest, Observable, of as observableOf, } from 'rxjs'; -import { map } from 'rxjs/operators'; -import { - AbstractMenuProvider, - MenuProvider, - MenuProviderTypeWithPaths, - MenuProviderTypeWithSubs, -} from '../menu-provider'; -import { Inject, Injector, Optional, Type } from '@angular/core'; -import { AbstractExpandableMenuProvider, MenuSubSection } from './expandable-menu-provider'; -import { ActivatedRoute, ActivatedRouteSnapshot, Router, RouterStateSnapshot } from '@angular/router'; -import { isEmpty } from '../../empty.util'; -import { MENU_PROVIDER } from '../menu.structure'; -import { MenuService } from '../menu.service'; - - -export abstract class AbstractExpandableParentMenuProvider extends AbstractMenuProvider { - isExpandable = true; - - public static withSubs(childProviders: (Type| MenuProviderTypeWithPaths)[]) { - if (!AbstractMenuProvider.isPrototypeOf(this)) { - throw new Error( - 'onRoute should only be called from concrete subclasses of AbstractMenuProvider' - ); - } - - const providerType = this as unknown as Type; - return {providerType: providerType, childProviderTypes: childProviders}; - } -} diff --git a/src/app/shared/menu/providers/export.menu.ts b/src/app/shared/menu/providers/export.menu.ts index 50f6667b38..77f7504a8e 100644 --- a/src/app/shared/menu/providers/export.menu.ts +++ b/src/app/shared/menu/providers/export.menu.ts @@ -8,26 +8,19 @@ import { Injectable } from '@angular/core'; import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; -import { - combineLatest as observableCombineLatest, - map, - Observable, - of as observableOf, -} from 'rxjs'; +import { combineLatest as observableCombineLatest, map, Observable, of as observableOf, } from 'rxjs'; import { AuthorizationDataService } from '../../../core/data/feature-authorization/authorization-data.service'; import { FeatureID } from '../../../core/data/feature-authorization/feature-id'; +import { METADATA_EXPORT_SCRIPT_NAME, ScriptDataService, } from '../../../core/data/processes/script-data.service'; import { - METADATA_EXPORT_SCRIPT_NAME, - ScriptDataService, -} from '../../../core/data/processes/script-data.service'; -import { ExportBatchSelectorComponent } from '../../dso-selector/modal-wrappers/export-batch-selector/export-batch-selector.component'; -import { ExportMetadataSelectorComponent } from '../../dso-selector/modal-wrappers/export-metadata-selector/export-metadata-selector.component'; + ExportBatchSelectorComponent +} from '../../dso-selector/modal-wrappers/export-batch-selector/export-batch-selector.component'; +import { + ExportMetadataSelectorComponent +} from '../../dso-selector/modal-wrappers/export-metadata-selector/export-metadata-selector.component'; import { MenuItemType } from '../menu-item-type.model'; -import { - AbstractExpandableMenuProvider, - MenuSubSection, - MenuTopSection, -} from './expandable-menu-provider'; +import { AbstractExpandableMenuProvider, } from './helper-providers/expandable-menu-provider'; +import { PartialMenuSection } from '../menu-provider'; @Injectable() export class ExportMenuProvider extends AbstractExpandableMenuProvider { @@ -39,7 +32,7 @@ export class ExportMenuProvider extends AbstractExpandableMenuProvider { super(); } - public getTopSection(): Observable { + public getTopSection(): Observable { return observableOf( { model: { @@ -47,12 +40,12 @@ export class ExportMenuProvider extends AbstractExpandableMenuProvider { text: 'menu.section.export', }, icon: 'file-export', - shouldPersistOnRouteChange: true, + visible: true, }, ); } - public getSubSections(): Observable { + public getSubSections(): Observable { return observableCombineLatest([ this.authorizationService.isAuthorized(FeatureID.AdministratorOf), this.scriptDataService.scriptWithNameExistsAndCanExecute(METADATA_EXPORT_SCRIPT_NAME), @@ -68,7 +61,6 @@ export class ExportMenuProvider extends AbstractExpandableMenuProvider { this.modalService.open(ExportMetadataSelectorComponent); }, }, - shouldPersistOnRouteChange: true, }, { visible: authorized && metadataExportScriptExists, @@ -79,9 +71,8 @@ export class ExportMenuProvider extends AbstractExpandableMenuProvider { this.modalService.open(ExportBatchSelectorComponent); }, }, - shouldPersistOnRouteChange: true, }, - ] as MenuSubSection[]; + ]; }), ); } diff --git a/src/app/shared/menu/providers/health.menu.ts b/src/app/shared/menu/providers/health.menu.ts index b3db57b3ac..bc7eef597e 100644 --- a/src/app/shared/menu/providers/health.menu.ts +++ b/src/app/shared/menu/providers/health.menu.ts @@ -7,18 +7,11 @@ */ import { Injectable } from '@angular/core'; -import { - combineLatest, - map, - Observable, -} from 'rxjs'; +import { combineLatest, map, Observable, } from 'rxjs'; import { AuthorizationDataService } from '../../../core/data/feature-authorization/authorization-data.service'; import { FeatureID } from '../../../core/data/feature-authorization/feature-id'; import { MenuItemType } from '../menu-item-type.model'; -import { - AbstractMenuProvider, - PartialMenuSection, -} from '../menu-provider'; +import { AbstractMenuProvider, PartialMenuSection, } from '../menu-provider'; @Injectable() export class HealthMenuProvider extends AbstractMenuProvider { diff --git a/src/app/shared/menu/providers/helper-providers/dso.menu.ts b/src/app/shared/menu/providers/helper-providers/dso.menu.ts new file mode 100644 index 0000000000..f88e452aff --- /dev/null +++ b/src/app/shared/menu/providers/helper-providers/dso.menu.ts @@ -0,0 +1,37 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +import { ActivatedRouteSnapshot, RouterStateSnapshot, } from '@angular/router'; +import { Observable, of } from 'rxjs'; +import { DSpaceObject } from '../../../../core/shared/dspace-object.model'; +import { AbstractRouteContextMenuProvider } from './route-context.menu'; +import { RemoteData } from '../../../../core/data/remote-data'; +import { hasValue } from '../../../empty.util'; + +export abstract class DSpaceObjectPageMenuProvider extends AbstractRouteContextMenuProvider { + + public getRouteContext(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable { + const dsoRD: RemoteData = route.data.dso; + if (hasValue(dsoRD) && dsoRD.hasSucceeded && hasValue(dsoRD.payload)) { + return of(dsoRD.payload); + } else { + return of(undefined); + } + } + + /** + * Retrieve the dso or entity type for an object to be used in generic messages + */ + protected getDsoType(dso: DSpaceObject) { + const renderType = dso.getRenderTypes()[0]; + if (typeof renderType === 'string' || renderType instanceof String) { + return renderType.toLowerCase(); + } else { + return dso.type.toString().toLowerCase(); + } + } +} diff --git a/src/app/shared/menu/providers/expandable-menu-provider.ts b/src/app/shared/menu/providers/helper-providers/expandable-menu-provider.ts similarity index 59% rename from src/app/shared/menu/providers/expandable-menu-provider.ts rename to src/app/shared/menu/providers/helper-providers/expandable-menu-provider.ts index 4671a1c1c5..276b892e6e 100644 --- a/src/app/shared/menu/providers/expandable-menu-provider.ts +++ b/src/app/shared/menu/providers/helper-providers/expandable-menu-provider.ts @@ -5,29 +5,18 @@ * * http://www.dspace.org/license/ */ -import { Omit } from '@material-ui/core'; -import { - combineLatest, - Observable, - of as observableOf, -} from 'rxjs'; +import { combineLatest, Observable, of as observableOf, } from 'rxjs'; import { map } from 'rxjs/operators'; -import { v4 as uuidv4 } from 'uuid'; -import { - AbstractMenuProvider, - PartialMenuSection, -} from '../menu-provider'; -import { Type } from '@angular/core'; - -export type MenuTopSection = Omit; -export type MenuSubSection = Omit; +import { AbstractMenuProvider, PartialMenuSection, } from '../../menu-provider'; export abstract class AbstractExpandableMenuProvider extends AbstractMenuProvider { - protected showWithoutSubsections = false; - abstract getTopSection(): Observable; + alwaysRenderExpandable = true; - abstract getSubSections(): Observable; + + abstract getTopSection(): Observable; + + abstract getSubSections(): Observable; protected includeSubSections(): boolean { return true; @@ -41,7 +30,7 @@ export abstract class AbstractExpandableMenuProvider extends AbstractMenuProvide full ? this.getSubSections() : observableOf([]), ]).pipe( map(( - [partialTopSection, partialSubSections]: [MenuTopSection, MenuSubSection[]] + [partialTopSection, partialSubSections]: [PartialMenuSection, PartialMenuSection[]] ) => { const subSections = partialSubSections.map((partialSub, index) => { return { @@ -56,7 +45,6 @@ export abstract class AbstractExpandableMenuProvider extends AbstractMenuProvide { ...partialTopSection, id: this.menuProviderId, - visible: full ? subSections.some(sub => sub.visible) : this.showWithoutSubsections, }, ]; }) diff --git a/src/app/shared/menu/providers/route-context.menu.ts b/src/app/shared/menu/providers/helper-providers/route-context.menu.ts similarity index 75% rename from src/app/shared/menu/providers/route-context.menu.ts rename to src/app/shared/menu/providers/helper-providers/route-context.menu.ts index 03e9c219f5..2ffc97ac5e 100644 --- a/src/app/shared/menu/providers/route-context.menu.ts +++ b/src/app/shared/menu/providers/helper-providers/route-context.menu.ts @@ -5,23 +5,14 @@ * * http://www.dspace.org/license/ */ -import { - ActivatedRouteSnapshot, - RouterStateSnapshot, -} from '@angular/router'; -import { - Observable, - of as observableOf, -} from 'rxjs'; -import { switchMap, tap } from 'rxjs/operators'; -import { hasValue } from '../../empty.util'; -import { - AbstractMenuProvider, - PartialMenuSection, -} from '../menu-provider'; +import { ActivatedRouteSnapshot, RouterStateSnapshot, } from '@angular/router'; +import { Observable, of as observableOf, } from 'rxjs'; +import { switchMap } from 'rxjs/operators'; +import { AbstractMenuProvider, PartialMenuSection, } from '../../menu-provider'; export abstract class AbstractRouteContextMenuProvider extends AbstractMenuProvider { shouldPersistOnRouteChange = false; + abstract getRouteContext(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable; abstract getSectionsForContext(routeContext: T): Observable; @@ -29,10 +20,8 @@ export abstract class AbstractRouteContextMenuProvider extends AbstractMenuPr getSections(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable { return this.getRouteContext(route, state).pipe( - switchMap((routeContext: T) => { if (this.isApplicable(routeContext)) { - return this.getSectionsForContext(routeContext); } else { return observableOf([]); diff --git a/src/app/shared/menu/providers/import.menu.ts b/src/app/shared/menu/providers/import.menu.ts index 5b1cfc0d0e..3e0cf3228e 100644 --- a/src/app/shared/menu/providers/import.menu.ts +++ b/src/app/shared/menu/providers/import.menu.ts @@ -8,24 +8,13 @@ import { Injectable } from '@angular/core'; import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; -import { - combineLatest as observableCombineLatest, - map, - Observable, - of as observableOf, -} from 'rxjs'; +import { combineLatest as observableCombineLatest, map, Observable, of as observableOf, } from 'rxjs'; import { AuthorizationDataService } from '../../../core/data/feature-authorization/authorization-data.service'; import { FeatureID } from '../../../core/data/feature-authorization/feature-id'; -import { - METADATA_IMPORT_SCRIPT_NAME, - ScriptDataService, -} from '../../../core/data/processes/script-data.service'; +import { METADATA_IMPORT_SCRIPT_NAME, ScriptDataService, } from '../../../core/data/processes/script-data.service'; import { MenuItemType } from '../menu-item-type.model'; -import { - AbstractExpandableMenuProvider, - MenuSubSection, - MenuTopSection, -} from './expandable-menu-provider'; +import { AbstractExpandableMenuProvider, } from './helper-providers/expandable-menu-provider'; +import { PartialMenuSection } from '../menu-provider'; @Injectable() export class ImportMenuProvider extends AbstractExpandableMenuProvider { @@ -37,7 +26,7 @@ export class ImportMenuProvider extends AbstractExpandableMenuProvider { super(); } - public getTopSection(): Observable { + public getTopSection(): Observable { return observableOf( { model: { @@ -45,12 +34,12 @@ export class ImportMenuProvider extends AbstractExpandableMenuProvider { text: 'menu.section.import', }, icon: 'file-import', - shouldPersistOnRouteChange: true, + visible: true, }, ); } - public getSubSections(): Observable { + public getSubSections(): Observable { return observableCombineLatest([ this.authorizationService.isAuthorized(FeatureID.AdministratorOf), this.scriptDataService.scriptWithNameExistsAndCanExecute(METADATA_IMPORT_SCRIPT_NAME), @@ -73,7 +62,7 @@ export class ImportMenuProvider extends AbstractExpandableMenuProvider { link: '/admin/batch-import', }, }, - ] as MenuSubSection[]; + ]; }), ); } diff --git a/src/app/shared/menu/providers/item-claim.menu.ts b/src/app/shared/menu/providers/item-claim.menu.ts index 4b1bf0ccfd..f2afb70ea7 100644 --- a/src/app/shared/menu/providers/item-claim.menu.ts +++ b/src/app/shared/menu/providers/item-claim.menu.ts @@ -8,12 +8,8 @@ import { Injectable } from '@angular/core'; import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; import { TranslateService } from '@ngx-translate/core'; -import { - combineLatest, - Observable, -} from 'rxjs'; +import { combineLatest, Observable, } from 'rxjs'; import { map } from 'rxjs/operators'; -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'; @@ -25,26 +21,28 @@ import { MenuItemType } from '../menu-item-type.model'; import { OnClickMenuItemModel } from '../menu-item/models/onclick.model'; import { PartialMenuSection } from '../menu-provider'; import { MenuService } from '../menu.service'; -import { DSpaceObjectPageMenuProvider } from './dso.menu'; +import { DSpaceObjectPageMenuProvider } from './helper-providers/dso.menu'; +import { DSpaceObject } from '../../../core/shared/dspace-object.model'; @Injectable() -// todo: the "Item-ness" of this class is basically unenforced though... -export class ClaimMenuProvider extends DSpaceObjectPageMenuProvider { +export class ClaimMenuProvider extends DSpaceObjectPageMenuProvider { constructor( protected authorizationService: AuthorizationDataService, protected menuService: MenuService, - protected dsoDataService: DSpaceObjectDataService, protected translate: TranslateService, protected notificationsService: NotificationsService, protected researcherProfileService: ResearcherProfileDataService, protected modalService: NgbModal, ) { - super(dsoDataService); + super(); } - protected isApplicable(item: Item): boolean { - return this.getDsoType(item) === 'person'; + protected isApplicable(item: DSpaceObject): boolean { + if (item instanceof Item) { + return this.getDsoType(item) === 'person'; + } + return false; } public getSectionsForContext(item: Item): Observable { diff --git a/src/app/shared/menu/providers/item-orcid.menu.ts b/src/app/shared/menu/providers/item-orcid.menu.ts index b1a9c6d9e7..1416b54a34 100644 --- a/src/app/shared/menu/providers/item-orcid.menu.ts +++ b/src/app/shared/menu/providers/item-orcid.menu.ts @@ -6,13 +6,9 @@ * http://www.dspace.org/license/ */ import { Injectable } from '@angular/core'; -import { - combineLatest, - Observable, -} from 'rxjs'; +import { combineLatest, Observable, } from 'rxjs'; import { map } 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 { Item } from '../../../core/shared/item.model'; @@ -20,19 +16,21 @@ import { URLCombiner } from '../../../core/url-combiner/url-combiner'; import { MenuItemType } from '../menu-item-type.model'; import { LinkMenuItemModel } from '../menu-item/models/link.model'; import { PartialMenuSection } from '../menu-provider'; -import { DSpaceObjectPageMenuProvider } from './dso.menu'; +import { DSpaceObjectPageMenuProvider } from './helper-providers/dso.menu'; @Injectable() -export class OrcidMenuProvider extends DSpaceObjectPageMenuProvider { +export class OrcidMenuProvider extends DSpaceObjectPageMenuProvider { constructor( protected authorizationService: AuthorizationDataService, - protected dsoDataService: DSpaceObjectDataService, ) { - super(dsoDataService); + super(); } protected isApplicable(item: Item): boolean { - return this.getDsoType(item) === 'person'; + if (item instanceof Item) { + return this.getDsoType(item) === 'person'; + } + return false; } public getSectionsForContext(item: Item): Observable { diff --git a/src/app/shared/menu/providers/item-versioning.menu.ts b/src/app/shared/menu/providers/item-versioning.menu.ts index 60766f5cbf..15349a7016 100644 --- a/src/app/shared/menu/providers/item-versioning.menu.ts +++ b/src/app/shared/menu/providers/item-versioning.menu.ts @@ -6,12 +6,8 @@ * http://www.dspace.org/license/ */ import { Injectable } from '@angular/core'; -import { - combineLatest, - Observable, -} from 'rxjs'; +import { combineLatest, Observable, } from 'rxjs'; import { map } from 'rxjs/operators'; -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 { Item } from '../../../core/shared/item.model'; @@ -19,16 +15,15 @@ import { DsoVersioningModalService } from '../../dso-page/dso-versioning-modal-s import { MenuItemType } from '../menu-item-type.model'; import { OnClickMenuItemModel } from '../menu-item/models/onclick.model'; import { PartialMenuSection } from '../menu-provider'; -import { DSpaceObjectPageMenuProvider } from './dso.menu'; +import { DSpaceObjectPageMenuProvider } from './helper-providers/dso.menu'; @Injectable() -export class VersioningMenuProvider extends DSpaceObjectPageMenuProvider { +export class VersioningMenuProvider extends DSpaceObjectPageMenuProvider { constructor( protected authorizationService: AuthorizationDataService, protected dsoVersioningModalService: DsoVersioningModalService, - protected dsoDataService: DSpaceObjectDataService, ) { - super(dsoDataService); + super(); } public getSectionsForContext(item: Item): Observable { diff --git a/src/app/shared/menu/providers/item.menu.ts b/src/app/shared/menu/providers/item.menu.ts deleted file mode 100644 index 5f889077af..0000000000 --- a/src/app/shared/menu/providers/item.menu.ts +++ /dev/null @@ -1,52 +0,0 @@ -/** - * The contents of this file are subject to the license and copyright - * detailed in the LICENSE and NOTICE files at the root of the source - * tree and available online at - * - * http://www.dspace.org/license/ - */ -import { Optional } from '@angular/core'; -import { - ActivatedRouteSnapshot, - RouterStateSnapshot, -} from '@angular/router'; -import { - Observable, - of, -} from 'rxjs'; -import { map } from 'rxjs/operators'; -import { DSpaceObjectDataService } from '../../../core/data/dspace-object-data.service'; -import { RemoteData } from '../../../core/data/remote-data'; -import { Item } from '../../../core/shared/item.model'; -import { getFirstCompletedRemoteData } from '../../../core/shared/operators'; -import { ItemPageResolver } from '../../../item-page/item-page.resolver'; -import { DSpaceObjectPageMenuProvider } from './dso.menu'; - -export abstract class ItemPageMenuProvider extends DSpaceObjectPageMenuProvider { - allRoutes = false; - - protected constructor( - protected dsoDataService: DSpaceObjectDataService, - @Optional() protected resolver?: ItemPageResolver, - ) { - super(dsoDataService); - } - - public getRouteContext(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable { - if (this.resolver === null) { - return of(undefined); - } - - // todo: it should be better to reuse the exact resolver that the page uses already, since the RD is guaranteed to be cached already - return (this.resolver.resolve(route, state) as Observable>).pipe( - getFirstCompletedRemoteData(), - map((dsoRD) => { - if (dsoRD.hasSucceeded) { - return dsoRD.payload; - } else { - return undefined; - } - }) - ); - } -} diff --git a/src/app/shared/menu/providers/new.menu.ts b/src/app/shared/menu/providers/new.menu.ts index e77f5535f9..07c54fa9cb 100644 --- a/src/app/shared/menu/providers/new.menu.ts +++ b/src/app/shared/menu/providers/new.menu.ts @@ -8,24 +8,22 @@ import { Injectable } from '@angular/core'; import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; -import { - combineLatest, - map, - Observable, - of as observableOf, -} from 'rxjs'; +import { combineLatest, map, Observable, of as observableOf, } from 'rxjs'; import { AuthorizationDataService } from '../../../core/data/feature-authorization/authorization-data.service'; import { FeatureID } from '../../../core/data/feature-authorization/feature-id'; -import { ThemedCreateCollectionParentSelectorComponent } from '../../dso-selector/modal-wrappers/create-collection-parent-selector/themed-create-collection-parent-selector.component'; -import { ThemedCreateCommunityParentSelectorComponent } from '../../dso-selector/modal-wrappers/create-community-parent-selector/themed-create-community-parent-selector.component'; -import { ThemedCreateItemParentSelectorComponent } from '../../dso-selector/modal-wrappers/create-item-parent-selector/themed-create-item-parent-selector.component'; +import { + ThemedCreateCollectionParentSelectorComponent +} from '../../dso-selector/modal-wrappers/create-collection-parent-selector/themed-create-collection-parent-selector.component'; +import { + ThemedCreateCommunityParentSelectorComponent +} from '../../dso-selector/modal-wrappers/create-community-parent-selector/themed-create-community-parent-selector.component'; +import { + ThemedCreateItemParentSelectorComponent +} from '../../dso-selector/modal-wrappers/create-item-parent-selector/themed-create-item-parent-selector.component'; import { MenuItemType } from '../menu-item-type.model'; import { TextMenuItemModel } from '../menu-item/models/text.model'; -import { - AbstractExpandableMenuProvider, - MenuSubSection, - MenuTopSection, -} from './expandable-menu-provider'; +import { AbstractExpandableMenuProvider, } from './helper-providers/expandable-menu-provider'; +import { PartialMenuSection } from '../menu-provider'; @Injectable() export class NewMenuProvider extends AbstractExpandableMenuProvider { @@ -36,7 +34,7 @@ export class NewMenuProvider extends AbstractExpandableMenuProvider { super(); } - public getTopSection(): Observable { + public getTopSection(): Observable { return observableOf( { model: { @@ -44,11 +42,12 @@ export class NewMenuProvider extends AbstractExpandableMenuProvider { text: 'menu.section.new' } as TextMenuItemModel, icon: 'plus', + visible: true, }, ); } - public getSubSections(): Observable { + public getSubSections(): Observable { return combineLatest([ this.authorizationService.isAuthorized(FeatureID.IsCollectionAdmin), this.authorizationService.isAuthorized(FeatureID.IsCommunityAdmin), @@ -95,7 +94,7 @@ export class NewMenuProvider extends AbstractExpandableMenuProvider { link: '/processes/new' }, }, - ] as MenuSubSection[]; + ]; })); } } diff --git a/src/app/shared/menu/providers/notifications.menu.ts b/src/app/shared/menu/providers/notifications.menu.ts deleted file mode 100644 index 7c189b8018..0000000000 --- a/src/app/shared/menu/providers/notifications.menu.ts +++ /dev/null @@ -1,68 +0,0 @@ -/** - * The contents of this file are subject to the license and copyright - * detailed in the LICENSE and NOTICE files at the root of the source - * tree and available online at - * - * http://www.dspace.org/license/ - */ - -import { Injectable } from '@angular/core'; -import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; -import { - combineLatest, - map, - Observable, - of as observableOf, -} from 'rxjs'; -import { AuthorizationDataService } from '../../../core/data/feature-authorization/authorization-data.service'; -import { FeatureID } from '../../../core/data/feature-authorization/feature-id'; -import { ScriptDataService } from '../../../core/data/processes/script-data.service'; -import { MenuItemType } from '../menu-item-type.model'; -import { - AbstractExpandableMenuProvider, - MenuSubSection, - MenuTopSection, -} from './expandable-menu-provider'; - -@Injectable() -export class NotificationsMenuProvider extends AbstractExpandableMenuProvider { - constructor( - protected authorizationService: AuthorizationDataService, - protected scriptDataService: ScriptDataService, - protected modalService: NgbModal, - ) { - super(); - } - - public getTopSection(): Observable { - return observableOf( - { - model: { - type: MenuItemType.TEXT, - text: 'menu.section.notifications', - }, - icon: 'bell', - }, - ); - } - - public getSubSections(): Observable { - return combineLatest([ - this.authorizationService.isAuthorized(FeatureID.AdministratorOf), - this.authorizationService.isAuthorized(FeatureID.CanSeeQA), - ]).pipe( - map(([authorized, canSeeQA]) => { - return [ - { - visible: authorized && canSeeQA, - model: { - type: MenuItemType.LINK, - text: 'menu.section.quality-assurance', - link: '/admin/notifications/quality-assurance', - }, - }, - ] as MenuSubSection[]; - }), - ); - } -} diff --git a/src/app/shared/menu/providers/processes.menu.ts b/src/app/shared/menu/providers/processes.menu.ts index 244c1b4f8e..6943572beb 100644 --- a/src/app/shared/menu/providers/processes.menu.ts +++ b/src/app/shared/menu/providers/processes.menu.ts @@ -7,18 +7,11 @@ */ import { Injectable } from '@angular/core'; -import { - combineLatest, - map, - Observable, -} from 'rxjs'; +import { combineLatest, map, Observable, } from 'rxjs'; import { AuthorizationDataService } from '../../../core/data/feature-authorization/authorization-data.service'; import { FeatureID } from '../../../core/data/feature-authorization/feature-id'; import { MenuItemType } from '../menu-item-type.model'; -import { - AbstractMenuProvider, - PartialMenuSection, -} from '../menu-provider'; +import { AbstractMenuProvider, PartialMenuSection, } from '../menu-provider'; @Injectable() export class ProcessesMenuProvider extends AbstractMenuProvider { diff --git a/src/app/shared/menu/providers/registries.menu.ts b/src/app/shared/menu/providers/registries.menu.ts index d2e7e2835d..d69aa79d0e 100644 --- a/src/app/shared/menu/providers/registries.menu.ts +++ b/src/app/shared/menu/providers/registries.menu.ts @@ -8,21 +8,13 @@ import { Injectable } from '@angular/core'; import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; -import { - combineLatest, - map, - Observable, - of as observableOf, -} from 'rxjs'; +import { combineLatest, map, Observable, of as observableOf, } from 'rxjs'; import { AuthorizationDataService } from '../../../core/data/feature-authorization/authorization-data.service'; import { FeatureID } from '../../../core/data/feature-authorization/feature-id'; import { ScriptDataService } from '../../../core/data/processes/script-data.service'; import { MenuItemType } from '../menu-item-type.model'; -import { - AbstractExpandableMenuProvider, - MenuSubSection, - MenuTopSection, -} from './expandable-menu-provider'; +import { AbstractExpandableMenuProvider, } from './helper-providers/expandable-menu-provider'; +import { PartialMenuSection } from '../menu-provider'; @Injectable() export class RegistriesMenuProvider extends AbstractExpandableMenuProvider { @@ -34,7 +26,7 @@ export class RegistriesMenuProvider extends AbstractExpandableMenuProvider { super(); } - public getTopSection(): Observable { + public getTopSection(): Observable { return observableOf( { model: { @@ -42,11 +34,12 @@ export class RegistriesMenuProvider extends AbstractExpandableMenuProvider { text: 'menu.section.registries', }, icon: 'list', + visible: true, }, ); } - public getSubSections(): Observable { + public getSubSections(): Observable { return combineLatest([ this.authorizationService.isAuthorized(FeatureID.AdministratorOf), ]).pipe( @@ -68,7 +61,7 @@ export class RegistriesMenuProvider extends AbstractExpandableMenuProvider { link: 'admin/registries/bitstream-formats', }, }, - ] as MenuSubSection[]; + ]; }), ); } diff --git a/src/app/shared/menu/providers/statistics.menu.ts b/src/app/shared/menu/providers/statistics.menu.ts index c3705fb77b..2d29cc575c 100644 --- a/src/app/shared/menu/providers/statistics.menu.ts +++ b/src/app/shared/menu/providers/statistics.menu.ts @@ -7,29 +7,15 @@ */ import { Injectable } from '@angular/core'; -import { - ActivatedRouteSnapshot, - RouterStateSnapshot, -} from '@angular/router'; -import { - Observable, - of, -} from 'rxjs'; +import { ActivatedRouteSnapshot, RouterStateSnapshot, } from '@angular/router'; +import { Observable, of, } from 'rxjs'; import { hasNoValue, hasValue } from '../../empty.util'; import { MenuItemType } from '../menu-item-type.model'; import { PartialMenuSection } from '../menu-provider'; -import { AbstractRouteContextMenuProvider } from './route-context.menu'; +import { AbstractRouteContextMenuProvider } from './helper-providers/route-context.menu'; import { DSpaceObject } from '../../../core/shared/dspace-object.model'; import { RemoteData } from '../../../core/data/remote-data'; -import { getFirstSucceededRemoteDataPayload } from '../../../core/shared/operators'; import { getDSORoute } from '../../../app-routing-paths'; -import { Community } from '../../../core/shared/community.model'; -import { getCommunityPageRoute } from '../../../community-page/community-page-routing-paths'; -import { Collection } from '../../../core/shared/collection.model'; -import { getCollectionPageRoute } from '../../../collection-page/collection-page-routing-paths'; -import { Item } from '../../../core/shared/item.model'; -import { getItemModuleRoute, getItemPageRoute } from '../../../item-page/item-page-routing-paths'; -import { URLCombiner } from '../../../core/url-combiner/url-combiner'; interface StatisticsLink { id: string, @@ -40,9 +26,9 @@ interface StatisticsLink { export class StatisticsMenuProvider extends AbstractRouteContextMenuProvider { public getRouteContext(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable { - - let dsoRD: RemoteData = route.data.dso - while (hasValue(route.parent) && hasNoValue(dsoRD) ) { + let dsoRD: RemoteData = route.data.dso; + // Check if one of the parent routes has a DSO + while (hasValue(route.parent) && hasNoValue(dsoRD)) { route = route.parent; dsoRD = route.data.dso; } @@ -52,32 +38,6 @@ export class StatisticsMenuProvider extends AbstractRouteContextMenuProvider { @@ -86,29 +46,12 @@ export class StatisticsMenuProvider extends AbstractRouteContextMenuProvider