mirror of
https://github.com/DSpace/dspace-angular.git
synced 2025-10-07 01:54:15 +00:00
Proof-of-concept: independent menu section providers
- Replace god-class resolvers with a service that populates the menus from lists of injectable providers - Static menu sections are resolved at the root route ~ `resolveStatic` - Route-dependent menu sections can be declared in the same structure, but are resolved on-demand ~ `resolveRoute` - More and more easily customizable - Parts can be moved between menus, removed, replaced or extended individually - The dependencies of each provider are independent of each other - Order of providers determines the order of each menu → single source of truth for the order
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import { Component, Inject, Injector, OnInit } from '@angular/core';
|
||||
import { MenuSectionComponent } from '../../../shared/menu/menu-section/menu-section.component';
|
||||
import { AbstractMenuSectionComponent } from '../../../shared/menu/menu-section/abstract-menu-section.component';
|
||||
import { MenuService } from '../../../shared/menu/menu.service';
|
||||
import { rendersSectionForMenu } from '../../../shared/menu/menu-section.decorator';
|
||||
import { LinkMenuItemModel } from '../../../shared/menu/menu-item/models/link.model';
|
||||
@@ -19,7 +19,7 @@ import { Router } from '@angular/router';
|
||||
|
||||
})
|
||||
@rendersSectionForMenu(MenuID.ADMIN, false)
|
||||
export class AdminSidebarSectionComponent extends MenuSectionComponent implements OnInit {
|
||||
export class AdminSidebarSectionComponent extends AbstractMenuSectionComponent implements OnInit {
|
||||
|
||||
/**
|
||||
* This section resides in the Admin Sidebar
|
||||
@@ -33,16 +33,17 @@ export class AdminSidebarSectionComponent extends MenuSectionComponent implement
|
||||
isDisabled: boolean;
|
||||
|
||||
constructor(
|
||||
@Inject('sectionDataProvider') menuSection: MenuSection,
|
||||
@Inject('sectionDataProvider') protected section: MenuSection,
|
||||
protected menuService: MenuService,
|
||||
protected injector: Injector,
|
||||
protected router: Router,
|
||||
) {
|
||||
super(menuSection, menuService, injector);
|
||||
this.itemModel = menuSection.model as LinkMenuItemModel;
|
||||
super(menuService, injector);
|
||||
this.itemModel = section.model as LinkMenuItemModel;
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
// todo: should support all menu entries?
|
||||
this.isDisabled = this.itemModel?.disabled || isEmpty(this.itemModel?.link);
|
||||
super.ngOnInit();
|
||||
}
|
||||
|
@@ -13,7 +13,7 @@
|
||||
(keyup.enter)="toggleSection($event)"
|
||||
>
|
||||
<div class="shortcut-icon h-100">
|
||||
<i class="fas fa-{{section.icon}} fa-fw"></i>
|
||||
<i class="fas fa-{{section.icon ?? 'notdef'}} fa-fw"></i>
|
||||
</div>
|
||||
<div class="sidebar-collapsible">
|
||||
<div class="toggle">
|
||||
|
@@ -1,5 +1,6 @@
|
||||
import { Component, Inject, Injector, OnInit } from '@angular/core';
|
||||
import { rotate } from '../../../shared/animations/rotate';
|
||||
import { MenuSection } from '../../../shared/menu/menu-section.model';
|
||||
import { AdminSidebarSectionComponent } from '../admin-sidebar-section/admin-sidebar-section.component';
|
||||
import { slide } from '../../../shared/animations/slide';
|
||||
import { CSSVariableService } from '../../../shared/sass-helper/css-variable.service';
|
||||
@@ -51,13 +52,13 @@ export class ExpandableAdminSidebarSectionComponent extends AdminSidebarSectionC
|
||||
expanded: Observable<boolean>;
|
||||
|
||||
constructor(
|
||||
@Inject('sectionDataProvider') menuSection,
|
||||
@Inject('sectionDataProvider') protected section: MenuSection,
|
||||
protected menuService: MenuService,
|
||||
private variableService: CSSVariableService,
|
||||
protected injector: Injector,
|
||||
protected router: Router,
|
||||
) {
|
||||
super(menuSection, menuService, injector, router);
|
||||
super(section, menuService, injector, router);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -1,11 +1,8 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { RouterModule, NoPreloading } from '@angular/router';
|
||||
import { AuthBlockingGuard } from './core/auth/auth-blocking.guard';
|
||||
|
||||
import { AuthenticatedGuard } from './core/auth/authenticated.guard';
|
||||
import {
|
||||
SiteAdministratorGuard
|
||||
} from './core/data/feature-authorization/feature-authorization-guard/site-administrator.guard';
|
||||
NoPreloading,
|
||||
RouterModule,
|
||||
} from '@angular/router';
|
||||
import {
|
||||
ACCESS_CONTROL_MODULE_PATH,
|
||||
ADMIN_MODULE_PATH,
|
||||
@@ -24,22 +21,24 @@ import {
|
||||
} from './app-routing-paths';
|
||||
import { COLLECTION_MODULE_PATH } from './collection-page/collection-page-routing-paths';
|
||||
import { COMMUNITY_MODULE_PATH } from './community-page/community-page-routing-paths';
|
||||
import { ITEM_MODULE_PATH } from './item-page/item-page-routing-paths';
|
||||
import { PROCESS_MODULE_PATH } from './process-page/process-page-routing.paths';
|
||||
import { ReloadGuard } from './core/reload/reload.guard';
|
||||
import { EndUserAgreementCurrentUserGuard } from './core/end-user-agreement/end-user-agreement-current-user.guard';
|
||||
import { AuthBlockingGuard } from './core/auth/auth-blocking.guard';
|
||||
|
||||
import { AuthenticatedGuard } from './core/auth/authenticated.guard';
|
||||
import { GroupAdministratorGuard } from './core/data/feature-authorization/feature-authorization-guard/group-administrator.guard';
|
||||
import { SiteAdministratorGuard } from './core/data/feature-authorization/feature-authorization-guard/site-administrator.guard';
|
||||
import { SiteRegisterGuard } from './core/data/feature-authorization/feature-authorization-guard/site-register.guard';
|
||||
import { ThemedPageNotFoundComponent } from './pagenotfound/themed-pagenotfound.component';
|
||||
import { ThemedForbiddenComponent } from './forbidden/themed-forbidden.component';
|
||||
import {
|
||||
GroupAdministratorGuard
|
||||
} from './core/data/feature-authorization/feature-authorization-guard/group-administrator.guard';
|
||||
import {
|
||||
ThemedPageInternalServerErrorComponent
|
||||
} from './page-internal-server-error/themed-page-internal-server-error.component';
|
||||
import { EndUserAgreementCurrentUserGuard } from './core/end-user-agreement/end-user-agreement-current-user.guard';
|
||||
import { ReloadGuard } from './core/reload/reload.guard';
|
||||
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 './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({
|
||||
imports: [
|
||||
@@ -50,7 +49,10 @@ import { ThemedPageErrorComponent } from './page-error/themed-page-error.compone
|
||||
path: '',
|
||||
canActivate: [AuthBlockingGuard],
|
||||
canActivateChild: [ServerCheckGuard],
|
||||
resolve: [MenuResolver],
|
||||
resolve: [
|
||||
resolveStaticMenus(),
|
||||
// MenuResolver,
|
||||
],
|
||||
children: [
|
||||
{ path: '', redirectTo: '/home', pathMatch: 'full' },
|
||||
{
|
||||
|
58
src/app/app.menus.ts
Normal file
58
src/app/app.menus.ts
Normal file
@@ -0,0 +1,58 @@
|
||||
/**
|
||||
* 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 { MenuID } from './shared/menu/menu-id.model';
|
||||
import { buildMenuStructure } from './shared/menu/menu.structure';
|
||||
import { AccessControlMenuProvider } from './shared/menu/providers/access-control.menu';
|
||||
import { AdminSearchMenuProvider } from './shared/menu/providers/admin-search.menu';
|
||||
import { BrowseMenuProvider } from './shared/menu/providers/browse.menu';
|
||||
import { SubscribeMenuProvider } from './shared/menu/providers/comcol-subscribe.menu';
|
||||
import { CommunityListMenuProvider } from './shared/menu/providers/community-list.menu';
|
||||
import { CurationMenuProvider } from './shared/menu/providers/curation.menu';
|
||||
import { DSpaceObjectEditMenuProvider } from './shared/menu/providers/dso-edit.menu';
|
||||
import { EditMenuProvider } from './shared/menu/providers/edit.menu';
|
||||
import { ExportMenuProvider } from './shared/menu/providers/export.menu';
|
||||
import { HealthMenuProvider } from './shared/menu/providers/health.menu';
|
||||
import { ImportMenuProvider } from './shared/menu/providers/import.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 { NewMenuProvider } from './shared/menu/providers/new.menu';
|
||||
import { ProcessesMenuProvider } from './shared/menu/providers/processes.menu';
|
||||
import { RegistriesMenuProvider } from './shared/menu/providers/registries.menu';
|
||||
import { StatisticsMenuProvider } from './shared/menu/providers/statistics.menu';
|
||||
import { SystemWideAlertMenuProvider } from './shared/menu/providers/system-wide-alert.menu';
|
||||
import { WorkflowMenuProvider } from './shared/menu/providers/workflow.menu';
|
||||
|
||||
export const MENUS = buildMenuStructure({
|
||||
[MenuID.PUBLIC]: [
|
||||
CommunityListMenuProvider,
|
||||
BrowseMenuProvider,
|
||||
StatisticsMenuProvider,
|
||||
],
|
||||
[MenuID.ADMIN]: [
|
||||
NewMenuProvider,
|
||||
EditMenuProvider,
|
||||
ImportMenuProvider,
|
||||
ExportMenuProvider,
|
||||
AccessControlMenuProvider,
|
||||
AdminSearchMenuProvider,
|
||||
RegistriesMenuProvider,
|
||||
CurationMenuProvider,
|
||||
ProcessesMenuProvider,
|
||||
WorkflowMenuProvider,
|
||||
HealthMenuProvider,
|
||||
SystemWideAlertMenuProvider,
|
||||
],
|
||||
[MenuID.DSO_EDIT]: [
|
||||
DSpaceObjectEditMenuProvider,
|
||||
VersioningMenuProvider,
|
||||
OrcidMenuProvider,
|
||||
ClaimMenuProvider,
|
||||
SubscribeMenuProvider,
|
||||
],
|
||||
});
|
@@ -1,35 +1,60 @@
|
||||
import { APP_BASE_HREF, CommonModule, DOCUMENT } from '@angular/common';
|
||||
import { HTTP_INTERCEPTORS, HttpClientModule } from '@angular/common/http';
|
||||
import {
|
||||
APP_BASE_HREF,
|
||||
CommonModule,
|
||||
DOCUMENT,
|
||||
} from '@angular/common';
|
||||
import {
|
||||
HTTP_INTERCEPTORS,
|
||||
HttpClientModule,
|
||||
} from '@angular/common/http';
|
||||
import { NgModule } from '@angular/core';
|
||||
import { BrowserModule } from '@angular/platform-browser';
|
||||
|
||||
import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
|
||||
import { DYNAMIC_MATCHER_PROVIDERS } from '@ng-dynamic-forms/core';
|
||||
import { EffectsModule } from '@ngrx/effects';
|
||||
import { RouterStateSerializer, StoreRouterConnectingModule } from '@ngrx/router-store';
|
||||
import { MetaReducer, StoreModule, USER_PROVIDED_META_REDUCERS } from '@ngrx/store';
|
||||
import {
|
||||
RouterStateSerializer,
|
||||
StoreRouterConnectingModule,
|
||||
} from '@ngrx/router-store';
|
||||
import {
|
||||
MetaReducer,
|
||||
StoreModule,
|
||||
USER_PROVIDED_META_REDUCERS,
|
||||
} from '@ngrx/store';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { ScrollToModule } from '@nicky-lenaers/ngx-scroll-to';
|
||||
import { DYNAMIC_MATCHER_PROVIDERS } from '@ng-dynamic-forms/core';
|
||||
import {
|
||||
APP_CONFIG,
|
||||
AppConfig,
|
||||
} from '../config/app-config.interface';
|
||||
import { StoreDevModules } from '../config/store/devtools';
|
||||
import { environment } from '../environments/environment';
|
||||
import { EagerThemesModule } from '../themes/eager-themes.module';
|
||||
|
||||
import { AppRoutingModule } from './app-routing.module';
|
||||
import { AppComponent } from './app.component';
|
||||
import { appEffects } from './app.effects';
|
||||
import { appMetaReducers, debugMetaReducers } from './app.metareducers';
|
||||
import { appReducers, AppState, storeModuleConfig } from './app.reducer';
|
||||
import { MENUS } from './app.menus';
|
||||
import {
|
||||
appMetaReducers,
|
||||
debugMetaReducers,
|
||||
} from './app.metareducers';
|
||||
import {
|
||||
appReducers,
|
||||
AppState,
|
||||
storeModuleConfig,
|
||||
} from './app.reducer';
|
||||
import { AuthInterceptor } from './core/auth/auth.interceptor';
|
||||
import { CoreModule } from './core/core.module';
|
||||
import { LocaleInterceptor } from './core/locale/locale.interceptor';
|
||||
import { LogInterceptor } from './core/log/log.interceptor';
|
||||
import { ClientCookieService } from './core/services/client-cookie.service';
|
||||
import { XsrfInterceptor } from './core/xsrf/xsrf.interceptor';
|
||||
import { NavbarModule } from './navbar/navbar.module';
|
||||
import { RootModule } from './root.module';
|
||||
import { DSpaceRouterStateSerializer } from './shared/ngrx/dspace-router-state-serializer';
|
||||
import { SharedModule } from './shared/shared.module';
|
||||
import { environment } from '../environments/environment';
|
||||
import { AuthInterceptor } from './core/auth/auth.interceptor';
|
||||
import { LocaleInterceptor } from './core/locale/locale.interceptor';
|
||||
import { XsrfInterceptor } from './core/xsrf/xsrf.interceptor';
|
||||
import { LogInterceptor } from './core/log/log.interceptor';
|
||||
import { EagerThemesModule } from '../themes/eager-themes.module';
|
||||
import { APP_CONFIG, AppConfig } from '../config/app-config.interface';
|
||||
import { StoreDevModules } from '../config/store/devtools';
|
||||
import { RootModule } from './root.module';
|
||||
|
||||
export function getConfig() {
|
||||
return environment;
|
||||
@@ -105,6 +130,9 @@ const PROVIDERS = [
|
||||
},
|
||||
// register the dynamic matcher used by form. MUST be provided by the app module
|
||||
...DYNAMIC_MATCHER_PROVIDERS,
|
||||
|
||||
// DI-composable menus
|
||||
...MENUS,
|
||||
];
|
||||
|
||||
const DECLARATIONS = [
|
||||
|
@@ -1,27 +1,28 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { RouterModule } from '@angular/router';
|
||||
import { AuthenticatedGuard } from '../core/auth/authenticated.guard';
|
||||
import { CollectionBreadcrumbResolver } from '../core/breadcrumbs/collection-breadcrumb.resolver';
|
||||
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 { resolveRouteMenus } from '../shared/menu/menu.resolver';
|
||||
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,
|
||||
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 { AuthenticatedGuard } from '../core/auth/authenticated.guard';
|
||||
import { CreateCollectionPageGuard } from './create-collection-page/create-collection-page.guard';
|
||||
import { DeleteCollectionPageComponent } from './delete-collection-page/delete-collection-page.component';
|
||||
import { ThemedEditItemTemplatePageComponent } from './edit-item-template-page/themed-edit-item-template-page.component';
|
||||
import { ItemTemplatePageResolver } from './edit-item-template-page/item-template-page.resolver';
|
||||
import { CollectionBreadcrumbResolver } from '../core/breadcrumbs/collection-breadcrumb.resolver';
|
||||
import { DSOBreadcrumbsService } from '../core/breadcrumbs/dso-breadcrumbs.service';
|
||||
import { LinkService } from '../core/cache/builders/link.service';
|
||||
import { I18nBreadcrumbResolver } from '../core/breadcrumbs/i18n-breadcrumb.resolver';
|
||||
import {
|
||||
ITEMTEMPLATE_PATH,
|
||||
COLLECTION_EDIT_PATH,
|
||||
COLLECTION_CREATE_PATH
|
||||
} from './collection-page-routing-paths';
|
||||
import { CollectionPageAdministratorGuard } from './collection-page-administrator.guard';
|
||||
import { LinkMenuItemModel } from '../shared/menu/menu-item/models/link.model';
|
||||
import { ThemedEditItemTemplatePageComponent } from './edit-item-template-page/themed-edit-item-template-page.component';
|
||||
import { ThemedCollectionPageComponent } from './themed-collection-page.component';
|
||||
import { MenuItemType } from '../shared/menu/menu-item-type.model';
|
||||
import { DSOEditMenuResolver } from '../shared/dso-page/dso-edit-menu.resolver';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
@@ -36,7 +37,11 @@ import { DSOEditMenuResolver } from '../shared/dso-page/dso-edit-menu.resolver';
|
||||
resolve: {
|
||||
dso: CollectionPageResolver,
|
||||
breadcrumb: CollectionBreadcrumbResolver,
|
||||
menu: DSOEditMenuResolver
|
||||
menu: resolveRouteMenus(
|
||||
StatisticsMenuProvider,
|
||||
DSpaceObjectEditMenuProvider,
|
||||
SubscribeMenuProvider,
|
||||
),
|
||||
},
|
||||
runGuardsAndResolvers: 'always',
|
||||
children: [
|
||||
@@ -68,21 +73,6 @@ import { DSOEditMenuResolver } from '../shared/dso-page/dso-edit-menu.resolver';
|
||||
pathMatch: 'full',
|
||||
}
|
||||
],
|
||||
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,
|
||||
}],
|
||||
},
|
||||
},
|
||||
},
|
||||
])
|
||||
],
|
||||
|
@@ -1,20 +1,25 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { RouterModule } from '@angular/router';
|
||||
|
||||
import { CommunityPageResolver } from './community-page.resolver';
|
||||
import { CreateCommunityPageComponent } from './create-community-page/create-community-page.component';
|
||||
import { AuthenticatedGuard } from '../core/auth/authenticated.guard';
|
||||
import { CreateCommunityPageGuard } from './create-community-page/create-community-page.guard';
|
||||
import { DeleteCommunityPageComponent } from './delete-community-page/delete-community-page.component';
|
||||
import { CommunityBreadcrumbResolver } from '../core/breadcrumbs/community-breadcrumb.resolver';
|
||||
import { DSOBreadcrumbsService } from '../core/breadcrumbs/dso-breadcrumbs.service';
|
||||
import { LinkService } from '../core/cache/builders/link.service';
|
||||
import { COMMUNITY_EDIT_PATH, COMMUNITY_CREATE_PATH } from './community-page-routing-paths';
|
||||
import { CommunityPageAdministratorGuard } from './community-page-administrator.guard';
|
||||
import { LinkMenuItemModel } from '../shared/menu/menu-item/models/link.model';
|
||||
import { ThemedCommunityPageComponent } from './themed-community-page.component';
|
||||
import { MenuItemType } from '../shared/menu/menu-item-type.model';
|
||||
import { DSOEditMenuResolver } from '../shared/dso-page/dso-edit-menu.resolver';
|
||||
import { resolveRouteMenus } from '../shared/menu/menu.resolver';
|
||||
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 { 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';
|
||||
import { ThemedCommunityPageComponent } from './themed-community-page.component';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
@@ -29,7 +34,12 @@ import { DSOEditMenuResolver } from '../shared/dso-page/dso-edit-menu.resolver';
|
||||
resolve: {
|
||||
dso: CommunityPageResolver,
|
||||
breadcrumb: CommunityBreadcrumbResolver,
|
||||
menu: DSOEditMenuResolver
|
||||
// menu: DSOEditMenuResolver,
|
||||
menu: resolveRouteMenus(
|
||||
StatisticsMenuProvider,
|
||||
DSpaceObjectEditMenuProvider,
|
||||
SubscribeMenuProvider,
|
||||
),
|
||||
},
|
||||
runGuardsAndResolvers: 'always',
|
||||
children: [
|
||||
@@ -51,21 +61,6 @@ import { DSOEditMenuResolver } from '../shared/dso-page/dso-edit-menu.resolver';
|
||||
pathMatch: 'full',
|
||||
}
|
||||
],
|
||||
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,
|
||||
}],
|
||||
},
|
||||
},
|
||||
},
|
||||
])
|
||||
],
|
||||
|
@@ -1,5 +1,8 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { RouterModule } from '@angular/router';
|
||||
import { MenuProviderService } from '../shared/menu/menu-provider.service';
|
||||
import { resolveRouteMenus } from '../shared/menu/menu.resolver';
|
||||
import { StatisticsMenuProvider } from '../shared/menu/providers/statistics.menu';
|
||||
|
||||
import { HomePageResolver } from './home-page.resolver';
|
||||
import { LinkMenuItemModel } from '../shared/menu/menu-item/models/link.model';
|
||||
@@ -15,22 +18,10 @@ import { MenuItemType } from '../shared/menu/menu-item-type.model';
|
||||
pathMatch: 'full',
|
||||
data: {
|
||||
title: 'home.title',
|
||||
menu: {
|
||||
public: [{
|
||||
id: 'statistics_site',
|
||||
active: true,
|
||||
visible: true,
|
||||
index: 2,
|
||||
model: {
|
||||
type: MenuItemType.LINK,
|
||||
text: 'menu.section.statistics',
|
||||
link: 'statistics',
|
||||
} as LinkMenuItemModel,
|
||||
}],
|
||||
},
|
||||
},
|
||||
resolve: {
|
||||
site: HomePageResolver
|
||||
site: HomePageResolver,
|
||||
menu: resolveRouteMenus(StatisticsMenuProvider), // todo: sometimes this doesn't show up!
|
||||
}
|
||||
}
|
||||
])
|
||||
|
@@ -1,24 +1,31 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { RouterModule } from '@angular/router';
|
||||
import { ItemPageResolver } from './item-page.resolver';
|
||||
import { AuthenticatedGuard } from '../core/auth/authenticated.guard';
|
||||
import { ItemBreadcrumbResolver } from '../core/breadcrumbs/item-breadcrumb.resolver';
|
||||
import { VersionResolver } from './version-page/version.resolver';
|
||||
import { DSOBreadcrumbsService } from '../core/breadcrumbs/dso-breadcrumbs.service';
|
||||
import { LinkService } from '../core/cache/builders/link.service';
|
||||
import { UploadBitstreamComponent } from './bitstreams/upload/upload-bitstream.component';
|
||||
import { ITEM_EDIT_PATH, ORCID_PATH, UPLOAD_BITSTREAM_PATH } from './item-page-routing-paths';
|
||||
import { ItemPageAdministratorGuard } from './item-page-administrator.guard';
|
||||
import { LinkMenuItemModel } from '../shared/menu/menu-item/models/link.model';
|
||||
import { ThemedItemPageComponent } from './simple/themed-item-page.component';
|
||||
import { ThemedFullItemPageComponent } from './full/themed-full-item-page.component';
|
||||
import { MenuItemType } from '../shared/menu/menu-item-type.model';
|
||||
import { VersionPageComponent } from './version-page/version-page/version-page.component';
|
||||
import { BitstreamRequestACopyPageComponent } from './bitstreams/request-a-copy/bitstream-request-a-copy-page.component';
|
||||
import { REQUEST_COPY_MODULE_PATH } from '../app-routing-paths';
|
||||
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 { resolveRouteMenus } from '../shared/menu/menu.resolver';
|
||||
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 { 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 { ItemPageResolver } from './item-page.resolver';
|
||||
import { OrcidPageComponent } from './orcid-page/orcid-page.component';
|
||||
import { OrcidPageGuard } from './orcid-page/orcid-page.guard';
|
||||
import { DSOEditMenuResolver } from '../shared/dso-page/dso-edit-menu.resolver';
|
||||
import { ThemedItemPageComponent } from './simple/themed-item-page.component';
|
||||
import { VersionPageComponent } from './version-page/version-page/version-page.component';
|
||||
import { VersionResolver } from './version-page/version.resolver';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
@@ -28,7 +35,13 @@ import { DSOEditMenuResolver } from '../shared/dso-page/dso-edit-menu.resolver';
|
||||
resolve: {
|
||||
dso: ItemPageResolver,
|
||||
breadcrumb: ItemBreadcrumbResolver,
|
||||
menu: DSOEditMenuResolver
|
||||
menu: resolveRouteMenus(
|
||||
StatisticsMenuProvider,
|
||||
OrcidMenuProvider,
|
||||
DSpaceObjectEditMenuProvider,
|
||||
ClaimMenuProvider,
|
||||
VersioningMenuProvider,
|
||||
),
|
||||
},
|
||||
runGuardsAndResolvers: 'always',
|
||||
children: [
|
||||
@@ -61,21 +74,6 @@ import { DSOEditMenuResolver } from '../shared/dso-page/dso-edit-menu.resolver';
|
||||
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',
|
||||
|
@@ -14,7 +14,9 @@ import { ItemResolver } from './item.resolver';
|
||||
* This class represents a resolver that requests a specific item before the route is activated and will redirect to the
|
||||
* entity page
|
||||
*/
|
||||
@Injectable()
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
})
|
||||
export class ItemPageResolver extends ItemResolver {
|
||||
constructor(
|
||||
protected itemService: ItemDataService,
|
||||
|
@@ -1,4 +1,5 @@
|
||||
import { Component, Inject, Injector, OnInit } from '@angular/core';
|
||||
import { MenuSection } from '../../shared/menu/menu-section.model';
|
||||
import { NavbarSectionComponent } from '../navbar-section/navbar-section.component';
|
||||
import { MenuService } from '../../shared/menu/menu.service';
|
||||
import { slide } from '../../shared/animations/slide';
|
||||
@@ -23,12 +24,13 @@ export class ExpandableNavbarSectionComponent extends NavbarSectionComponent imp
|
||||
*/
|
||||
menuID = MenuID.PUBLIC;
|
||||
|
||||
constructor(@Inject('sectionDataProvider') menuSection,
|
||||
protected menuService: MenuService,
|
||||
protected injector: Injector,
|
||||
private windowService: HostWindowService
|
||||
constructor(
|
||||
@Inject('sectionDataProvider') protected section: MenuSection,
|
||||
protected menuService: MenuService,
|
||||
protected injector: Injector,
|
||||
private windowService: HostWindowService,
|
||||
) {
|
||||
super(menuSection, menuService, injector);
|
||||
super(section, menuService, injector);
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
|
@@ -1,5 +1,6 @@
|
||||
import { Component, Inject, Injector, OnInit } from '@angular/core';
|
||||
import { MenuSectionComponent } from '../../shared/menu/menu-section/menu-section.component';
|
||||
import { MenuSection } from '../../shared/menu/menu-section.model';
|
||||
import { AbstractMenuSectionComponent } from '../../shared/menu/menu-section/abstract-menu-section.component';
|
||||
import { MenuService } from '../../shared/menu/menu.service';
|
||||
import { rendersSectionForMenu } from '../../shared/menu/menu-section.decorator';
|
||||
import { MenuID } from '../../shared/menu/menu-id.model';
|
||||
@@ -14,17 +15,18 @@ import { MenuID } from '../../shared/menu/menu-id.model';
|
||||
styleUrls: ['./navbar-section.component.scss']
|
||||
})
|
||||
@rendersSectionForMenu(MenuID.PUBLIC, false)
|
||||
export class NavbarSectionComponent extends MenuSectionComponent implements OnInit {
|
||||
export class NavbarSectionComponent extends AbstractMenuSectionComponent implements OnInit {
|
||||
/**
|
||||
* This section resides in the Public Navbar
|
||||
*/
|
||||
menuID = MenuID.PUBLIC;
|
||||
|
||||
constructor(@Inject('sectionDataProvider') menuSection,
|
||||
protected menuService: MenuService,
|
||||
protected injector: Injector
|
||||
constructor(
|
||||
@Inject('sectionDataProvider') protected section: MenuSection,
|
||||
protected menuService: MenuService,
|
||||
protected injector: Injector,
|
||||
) {
|
||||
super(menuSection, menuService, injector);
|
||||
super(menuService, injector);
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
|
@@ -3,9 +3,21 @@
|
||||
}
|
||||
|
||||
.dso-button-menu {
|
||||
// todo: random thought to make dso page dropdown buttons clear
|
||||
.dropdown-toggle::after {
|
||||
display: none;
|
||||
content: '';
|
||||
width: 0;
|
||||
height: 0;
|
||||
border-style: solid;
|
||||
border-width: 12px 12px 0 0;
|
||||
border-color: transparent #627a91 transparent transparent;
|
||||
border-bottom-right-radius: var(--bs-btn-border-radius-sm);
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
position: absolute;
|
||||
overflow: hidden;
|
||||
}
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
ul.dropdown-menu {
|
||||
|
@@ -1,6 +1,6 @@
|
||||
import { Component, Inject, Injector } from '@angular/core';
|
||||
import { rendersSectionForMenu } from 'src/app/shared/menu/menu-section.decorator';
|
||||
import { MenuSectionComponent } from 'src/app/shared/menu/menu-section/menu-section.component';
|
||||
import { AbstractMenuSectionComponent } from 'src/app/shared/menu/menu-section/abstract-menu-section.component';
|
||||
import { MenuService } from '../../../menu/menu.service';
|
||||
import { Router } from '@angular/router';
|
||||
import { MenuID } from 'src/app/shared/menu/menu-id.model';
|
||||
@@ -19,7 +19,7 @@ import { hasValue } from '../../../empty.util';
|
||||
styleUrls: ['./dso-edit-menu-expandable-section.component.scss'],
|
||||
})
|
||||
@rendersSectionForMenu(MenuID.DSO_EDIT, true)
|
||||
export class DsoEditMenuExpandableSectionComponent extends MenuSectionComponent {
|
||||
export class DsoEditMenuExpandableSectionComponent extends AbstractMenuSectionComponent {
|
||||
|
||||
menuID: MenuID = MenuID.DSO_EDIT;
|
||||
itemModel;
|
||||
@@ -27,13 +27,13 @@ export class DsoEditMenuExpandableSectionComponent extends MenuSectionComponent
|
||||
renderIcons$: Observable<boolean>;
|
||||
|
||||
constructor(
|
||||
@Inject('sectionDataProvider') menuSection: MenuSection,
|
||||
@Inject('sectionDataProvider') protected section: MenuSection,
|
||||
protected menuService: MenuService,
|
||||
protected injector: Injector,
|
||||
protected router: Router,
|
||||
) {
|
||||
super(menuSection, menuService, injector);
|
||||
this.itemModel = menuSection.model;
|
||||
super(menuService, injector);
|
||||
this.itemModel = section.model;
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
|
@@ -1,6 +1,6 @@
|
||||
import { Component, Inject, Injector, OnInit } from '@angular/core';
|
||||
import { rendersSectionForMenu } from 'src/app/shared/menu/menu-section.decorator';
|
||||
import { MenuSectionComponent } from 'src/app/shared/menu/menu-section/menu-section.component';
|
||||
import { AbstractMenuSectionComponent } from 'src/app/shared/menu/menu-section/abstract-menu-section.component';
|
||||
import { MenuService } from '../../../menu/menu.service';
|
||||
import { isNotEmpty } from '../../../empty.util';
|
||||
import { MenuID } from '../../../menu/menu-id.model';
|
||||
@@ -16,7 +16,7 @@ import { MenuSection } from '../../../menu/menu-section.model';
|
||||
styleUrls: ['./dso-edit-menu-section.component.scss']
|
||||
})
|
||||
@rendersSectionForMenu(MenuID.DSO_EDIT, false)
|
||||
export class DsoEditMenuSectionComponent extends MenuSectionComponent implements OnInit {
|
||||
export class DsoEditMenuSectionComponent extends AbstractMenuSectionComponent implements OnInit {
|
||||
|
||||
menuID: MenuID = MenuID.DSO_EDIT;
|
||||
itemModel;
|
||||
@@ -24,12 +24,12 @@ export class DsoEditMenuSectionComponent extends MenuSectionComponent implements
|
||||
canActivate: boolean;
|
||||
|
||||
constructor(
|
||||
@Inject('sectionDataProvider') menuSection: MenuSection,
|
||||
@Inject('sectionDataProvider') protected section: MenuSection,
|
||||
protected menuService: MenuService,
|
||||
protected injector: Injector,
|
||||
) {
|
||||
super(menuSection, menuService, injector);
|
||||
this.itemModel = menuSection.model;
|
||||
super(menuService, injector);
|
||||
this.itemModel = section.model;
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
|
@@ -6,6 +6,6 @@ import { MenuItemType } from '../../menu-item-type.model';
|
||||
*/
|
||||
export class AltmetricMenuItemModel implements MenuItemModel {
|
||||
type = MenuItemType.ALTMETRIC;
|
||||
disabled: boolean;
|
||||
disabled?: boolean;
|
||||
url: string;
|
||||
}
|
||||
|
@@ -6,7 +6,7 @@ import { MenuItemType } from '../../menu-item-type.model';
|
||||
*/
|
||||
export class ExternalLinkMenuItemModel implements MenuItemModel {
|
||||
type = MenuItemType.EXTERNAL;
|
||||
disabled: boolean;
|
||||
disabled?: boolean;
|
||||
text: string;
|
||||
href: string;
|
||||
}
|
||||
|
@@ -7,7 +7,7 @@ import { Params } from '@angular/router';
|
||||
*/
|
||||
export class LinkMenuItemModel implements MenuItemModel {
|
||||
type = MenuItemType.LINK;
|
||||
disabled: boolean;
|
||||
disabled?: boolean;
|
||||
text: string;
|
||||
link: string;
|
||||
queryParams?: Params | null;
|
||||
|
@@ -5,5 +5,5 @@ import { MenuItemType } from '../../menu-item-type.model';
|
||||
*/
|
||||
export interface MenuItemModel {
|
||||
type: MenuItemType;
|
||||
disabled: boolean;
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
@@ -6,7 +6,7 @@ import { MenuItemType } from '../../menu-item-type.model';
|
||||
*/
|
||||
export class OnClickMenuItemModel implements MenuItemModel {
|
||||
type = MenuItemType.ONCLICK;
|
||||
disabled: boolean;
|
||||
disabled?: boolean;
|
||||
text: string;
|
||||
function: () => {};
|
||||
function: () => void;
|
||||
}
|
||||
|
@@ -1,12 +1,12 @@
|
||||
import { MenuItemModel } from './menu-item.model';
|
||||
import { MenuItemType } from '../../menu-item-type.model';
|
||||
import { MenuItemModel } from './menu-item.model';
|
||||
|
||||
/**
|
||||
* Model representing an Search Bar Menu Section
|
||||
*/
|
||||
export class SearchMenuItemModel implements MenuItemModel {
|
||||
type = MenuItemType.SEARCH;
|
||||
disabled: boolean;
|
||||
disabled?: boolean;
|
||||
placeholder: string;
|
||||
action: string;
|
||||
}
|
||||
|
@@ -6,6 +6,6 @@ import { MenuItemType } from '../../menu-item-type.model';
|
||||
*/
|
||||
export class TextMenuItemModel implements MenuItemModel {
|
||||
type = MenuItemType.TEXT;
|
||||
disabled: boolean;
|
||||
disabled?: boolean;
|
||||
text: string;
|
||||
}
|
||||
|
126
src/app/shared/menu/menu-provider.service.ts
Normal file
126
src/app/shared/menu/menu-provider.service.ts
Normal file
@@ -0,0 +1,126 @@
|
||||
/**
|
||||
* 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 {
|
||||
Inject,
|
||||
Injectable,
|
||||
Injector,
|
||||
Optional,
|
||||
Type,
|
||||
} from '@angular/core';
|
||||
import {
|
||||
ActivatedRouteSnapshot,
|
||||
RouterStateSnapshot,
|
||||
} from '@angular/router';
|
||||
import {
|
||||
combineLatest,
|
||||
forkJoin,
|
||||
map,
|
||||
Observable,
|
||||
} from 'rxjs';
|
||||
import {
|
||||
find,
|
||||
switchMap,
|
||||
take,
|
||||
tap,
|
||||
} from 'rxjs/operators';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import { hasValue } from '../empty.util';
|
||||
import { MenuID } from './menu-id.model';
|
||||
import { AbstractMenuProvider } from './menu-provider';
|
||||
import { MenuSection } from './menu-section.model';
|
||||
import { MenuState } from './menu-state.model';
|
||||
import { MenuService } from './menu.service';
|
||||
import { MENU_PROVIDER } from './menu.structure';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
})
|
||||
export class MenuProviderService {
|
||||
constructor(
|
||||
@Inject(MENU_PROVIDER) @Optional() protected providers: ReadonlyArray<AbstractMenuProvider>,
|
||||
protected menuService: MenuService,
|
||||
protected injector: Injector,
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Wait for a specific menu to appear
|
||||
* @param id the ID of the menu to wait for
|
||||
* @return an Observable that emits true as soon as the menu is created
|
||||
*/
|
||||
protected waitForMenu$(id: MenuID): Observable<boolean> {
|
||||
return this.menuService.getMenu(id).pipe(
|
||||
find((menu: MenuState) => hasValue(menu)),
|
||||
map(() => true),
|
||||
);
|
||||
}
|
||||
|
||||
public resolveStaticMenu(
|
||||
route: ActivatedRouteSnapshot,
|
||||
state: RouterStateSnapshot,
|
||||
): Observable<boolean> {
|
||||
return combineLatest(
|
||||
this.providers
|
||||
.filter(p => p.allRoutes)
|
||||
.map(provider => provider.getSections(route, state).pipe(
|
||||
tap((sections) => {
|
||||
sections.forEach((section: MenuSection) => {
|
||||
this.menuService.addSection(provider.menuID, {
|
||||
...section,
|
||||
id: section.id ?? uuidv4(),
|
||||
index: section.index ?? provider.index,
|
||||
shouldPersistOnRouteChange: true,
|
||||
});
|
||||
});
|
||||
}),
|
||||
switchMap(() => this.waitForMenu$(provider.menuID)),
|
||||
)),
|
||||
).pipe(
|
||||
map(done => done.every(Boolean)),
|
||||
);
|
||||
}
|
||||
|
||||
public resolveRouteMenu(
|
||||
route: ActivatedRouteSnapshot,
|
||||
state: RouterStateSnapshot,
|
||||
...providerTypes: Type<AbstractMenuProvider>[]
|
||||
): Observable<any> {
|
||||
// todo: no why please no
|
||||
return forkJoin(
|
||||
providerTypes.map(pt => this.injector.get(pt))
|
||||
.map((provider: AbstractMenuProvider) => provider.getSections(route, state).pipe(
|
||||
map(sections => [
|
||||
provider.menuID,
|
||||
sections.map((section: MenuSection) => {
|
||||
return {
|
||||
...section,
|
||||
id: section.id ?? uuidv4(),
|
||||
index: section.index ?? provider.index,
|
||||
shouldPersistOnRouteChange: false,
|
||||
};
|
||||
}),
|
||||
]),
|
||||
take(1),
|
||||
)),
|
||||
).pipe(
|
||||
map((entries) => {
|
||||
return entries.reduce((all, entry) => {
|
||||
const menuID = entry[0] as unknown as MenuID;
|
||||
const sections = entry[1] as unknown as MenuSection[];
|
||||
|
||||
if (all[menuID] === undefined) {
|
||||
all[menuID] = [];
|
||||
}
|
||||
all[menuID].push(...sections);
|
||||
return all;
|
||||
}, {});
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
47
src/app/shared/menu/menu-provider.ts
Normal file
47
src/app/shared/menu/menu-provider.ts
Normal file
@@ -0,0 +1,47 @@
|
||||
/**
|
||||
* 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 { Omit } from '@material-ui/core';
|
||||
import flatten from 'lodash/flatten';
|
||||
import {
|
||||
combineLatest,
|
||||
Observable,
|
||||
} from 'rxjs';
|
||||
import { map } from 'rxjs/operators';
|
||||
import { MenuID } from './menu-id.model';
|
||||
import { MenuSection } from './menu-section.model';
|
||||
|
||||
export type PartialMenuSection = Omit<MenuSection, 'id' | 'active'>;
|
||||
|
||||
export interface MenuProvider {
|
||||
allRoutes?: boolean,
|
||||
menuID?: MenuID;
|
||||
index?: number;
|
||||
|
||||
getSections(route?: ActivatedRouteSnapshot, state?: RouterStateSnapshot): Observable<PartialMenuSection[]>;
|
||||
}
|
||||
|
||||
export abstract class AbstractMenuProvider implements MenuProvider {
|
||||
public allRoutes = true;
|
||||
menuID?: MenuID;
|
||||
index?: number;
|
||||
|
||||
abstract getSections(route?: ActivatedRouteSnapshot, state?: RouterStateSnapshot): Observable<PartialMenuSection[]>;
|
||||
|
||||
protected concat(...sections$: Observable<PartialMenuSection[]>[]): Observable<PartialMenuSection[]> {
|
||||
return combineLatest(sections$).pipe(
|
||||
map(sections => flatten(sections)),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -1,15 +1,82 @@
|
||||
import { MenuItemModel } from './menu-item/models/menu-item.model';
|
||||
import { MenuItemType } from './menu-item-type.model';
|
||||
import { AltmetricMenuItemModel } from './menu-item/models/altmetric.model';
|
||||
import { ExternalLinkMenuItemModel } from './menu-item/models/external-link.model';
|
||||
import { LinkMenuItemModel } from './menu-item/models/link.model';
|
||||
import { OnClickMenuItemModel } from './menu-item/models/onclick.model';
|
||||
import { SearchMenuItemModel } from './menu-item/models/search.model';
|
||||
import { TextMenuItemModel } from './menu-item/models/text.model';
|
||||
|
||||
/**
|
||||
* Represents the state of a single menu section in the store
|
||||
*/
|
||||
export class MenuSection {
|
||||
id: string;
|
||||
parentID?: string;
|
||||
visible: boolean;
|
||||
active: boolean;
|
||||
model: MenuItemModel;
|
||||
index?: number;
|
||||
icon?: string;
|
||||
shouldPersistOnRouteChange? = false;
|
||||
export type MenuItemModels =
|
||||
LinkMenuItemModel
|
||||
| AltmetricMenuItemModel
|
||||
| ExternalLinkMenuItemModel
|
||||
| OnClickMenuItemModel
|
||||
| SearchMenuItemModel
|
||||
| TextMenuItemModel;
|
||||
|
||||
function itemModelFactory(type: MenuItemType): MenuItemModels {
|
||||
switch (type) {
|
||||
case MenuItemType.TEXT:
|
||||
return new TextMenuItemModel();
|
||||
case MenuItemType.LINK:
|
||||
return new LinkMenuItemModel();
|
||||
case MenuItemType.ALTMETRIC:
|
||||
return new AltmetricMenuItemModel();
|
||||
case MenuItemType.SEARCH:
|
||||
return new SearchMenuItemModel();
|
||||
case MenuItemType.ONCLICK:
|
||||
return new OnClickMenuItemModel();
|
||||
case MenuItemType.EXTERNAL:
|
||||
return new ExternalLinkMenuItemModel();
|
||||
default: {
|
||||
throw new Error(`No such menu item type: ${type}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export interface MenuSection {
|
||||
/**
|
||||
* The identifier for this section
|
||||
*/
|
||||
id: string;
|
||||
|
||||
/**
|
||||
* Whether this section should be visible.
|
||||
*/
|
||||
visible: boolean;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
model: MenuItemModels;
|
||||
|
||||
/**
|
||||
* The identifier of this section's parent section (optional).
|
||||
*/
|
||||
parentID?: string;
|
||||
|
||||
/**
|
||||
* The index of this section in its menu.
|
||||
*/
|
||||
index?: number;
|
||||
|
||||
/**
|
||||
* Whether this section is currently active.
|
||||
* Newly created sections are inactive until toggled.
|
||||
*/
|
||||
active?: boolean;
|
||||
|
||||
/**
|
||||
* Whether this section is independent of the route (default: true).
|
||||
* This value should be set explicitly for route-dependent sections.
|
||||
*/
|
||||
shouldPersistOnRouteChange?: boolean;
|
||||
|
||||
|
||||
/**
|
||||
* An optional icon for this section.
|
||||
* Must correspond to a FontAwesome icon class save for the `.fa-` prefix.
|
||||
* Note that not all menus may render icons.
|
||||
*/
|
||||
icon?: string;
|
||||
}
|
||||
|
@@ -1,14 +1,29 @@
|
||||
import { Component, Injector, OnDestroy, OnInit } from '@angular/core';
|
||||
import { MenuService } from '../menu.service';
|
||||
import { getComponentForMenuItemType } from '../menu-item.decorator';
|
||||
import { hasNoValue, hasValue } from '../../empty.util';
|
||||
import { BehaviorSubject, Observable, Subscription } from 'rxjs';
|
||||
import { MenuItemModel } from '../menu-item/models/menu-item.model';
|
||||
import { distinctUntilChanged, switchMap } from 'rxjs/operators';
|
||||
import {
|
||||
Component,
|
||||
Injector,
|
||||
OnDestroy,
|
||||
OnInit,
|
||||
} from '@angular/core';
|
||||
import {
|
||||
BehaviorSubject,
|
||||
Observable,
|
||||
Subscription,
|
||||
} from 'rxjs';
|
||||
import {
|
||||
distinctUntilChanged,
|
||||
switchMap,
|
||||
} from 'rxjs/operators';
|
||||
import { GenericConstructor } from '../../../core/shared/generic-constructor';
|
||||
import { MenuSection } from '../menu-section.model';
|
||||
import {
|
||||
hasNoValue,
|
||||
hasValue,
|
||||
} from '../../empty.util';
|
||||
import { MenuID } from '../menu-id.model';
|
||||
import { MenuItemType } from '../menu-item-type.model';
|
||||
import { getComponentForMenuItemType } from '../menu-item.decorator';
|
||||
import { MenuItemModel } from '../menu-item/models/menu-item.model';
|
||||
import { MenuSection } from '../menu-section.model';
|
||||
import { MenuService } from '../menu.service';
|
||||
|
||||
/**
|
||||
* A basic implementation of a menu section's component
|
||||
@@ -17,7 +32,8 @@ import { MenuItemType } from '../menu-item-type.model';
|
||||
selector: 'ds-menu-section',
|
||||
template: ''
|
||||
})
|
||||
export class MenuSectionComponent implements OnInit, OnDestroy {
|
||||
export abstract class AbstractMenuSectionComponent implements OnInit, OnDestroy {
|
||||
protected abstract section: MenuSection;
|
||||
|
||||
/**
|
||||
* Observable that emits whether or not this section is currently active
|
||||
@@ -39,7 +55,7 @@ export class MenuSectionComponent implements OnInit, OnDestroy {
|
||||
*/
|
||||
sectionMap$: BehaviorSubject<Map<string, {
|
||||
injector: Injector,
|
||||
component: GenericConstructor<MenuSectionComponent>
|
||||
component: GenericConstructor<AbstractMenuSectionComponent>
|
||||
}>> = new BehaviorSubject(new Map());
|
||||
|
||||
/**
|
||||
@@ -48,7 +64,10 @@ export class MenuSectionComponent implements OnInit, OnDestroy {
|
||||
*/
|
||||
subs: Subscription[] = [];
|
||||
|
||||
constructor(public section: MenuSection, protected menuService: MenuService, protected injector: Injector) {
|
||||
protected constructor(
|
||||
protected menuService: MenuService,
|
||||
protected injector: Injector,
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
@@ -1,17 +1,38 @@
|
||||
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, Injector, NO_ERRORS_SCHEMA } from '@angular/core';
|
||||
import { MenuSectionComponent } from './menu-section.component';
|
||||
import {
|
||||
ChangeDetectionStrategy,
|
||||
Component,
|
||||
Inject,
|
||||
Injector,
|
||||
NO_ERRORS_SCHEMA,
|
||||
} from '@angular/core';
|
||||
import { AbstractMenuSectionComponent } from './abstract-menu-section.component';
|
||||
import { MenuService } from '../menu.service';
|
||||
import { MenuServiceStub } from '../../testing/menu-service.stub';
|
||||
import { of as observableOf } from 'rxjs';
|
||||
import { LinkMenuItemComponent } from '../menu-item/link-menu-item.component';
|
||||
import { MenuSection } from '../menu-section.model';
|
||||
|
||||
@Component({
|
||||
selector: 'ds-some-menu-section',
|
||||
template: '',
|
||||
})
|
||||
class SomeMenuSectionComponent extends AbstractMenuSectionComponent {
|
||||
constructor(
|
||||
@Inject('sectionDataProvider') protected section: MenuSection,
|
||||
protected menuService: MenuService,
|
||||
protected injector: Injector,
|
||||
) {
|
||||
super(menuService, injector);
|
||||
}
|
||||
}
|
||||
|
||||
describe('MenuSectionComponent', () => {
|
||||
let comp: MenuSectionComponent;
|
||||
let fixture: ComponentFixture<MenuSectionComponent>;
|
||||
let comp: AbstractMenuSectionComponent;
|
||||
let fixture: ComponentFixture<AbstractMenuSectionComponent>;
|
||||
let menuService: MenuService;
|
||||
let dummySection;
|
||||
|
||||
@@ -23,20 +44,20 @@ describe('MenuSectionComponent', () => {
|
||||
} as any;
|
||||
TestBed.configureTestingModule({
|
||||
imports: [TranslateModule.forRoot(), NoopAnimationsModule],
|
||||
declarations: [MenuSectionComponent],
|
||||
declarations: [AbstractMenuSectionComponent],
|
||||
providers: [
|
||||
{ provide: Injector, useValue: {} },
|
||||
{ provide: MenuService, useClass: MenuServiceStub },
|
||||
{ provide: MenuSection, useValue: dummySection },
|
||||
{ provide: 'sectionDataProvider', useValue: dummySection },
|
||||
],
|
||||
schemas: [NO_ERRORS_SCHEMA]
|
||||
}).overrideComponent(MenuSectionComponent, {
|
||||
}).overrideComponent(SomeMenuSectionComponent, {
|
||||
set: { changeDetection: ChangeDetectionStrategy.Default }
|
||||
}).compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(MenuSectionComponent);
|
||||
fixture = TestBed.createComponent(SomeMenuSectionComponent);
|
||||
comp = fixture.componentInstance;
|
||||
menuService = (comp as any).menuService;
|
||||
spyOn(comp as any, 'getMenuItemComponent').and.returnValue(LinkMenuItemComponent);
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import { MenuSectionIndex } from './menu-section-Index.model';
|
||||
import { MenuSectionIndex } from './menu-section-index.model';
|
||||
import { MenuSections } from './menu-sections.model';
|
||||
import { MenuID } from './menu-id.model';
|
||||
|
||||
|
@@ -4,7 +4,7 @@ import { MenuService } from './menu.service';
|
||||
import { distinctUntilChanged, map, mergeMap, switchMap } from 'rxjs/operators';
|
||||
import { GenericConstructor } from '../../core/shared/generic-constructor';
|
||||
import { hasValue, isNotEmptyOperator } from '../empty.util';
|
||||
import { MenuSectionComponent } from './menu-section/menu-section.component';
|
||||
import { AbstractMenuSectionComponent } from './menu-section/abstract-menu-section.component';
|
||||
import { getComponentForMenu } from './menu-section.decorator';
|
||||
import { compareArraysUsingIds } from '../../item-page/simple/item-types/shared/item-relationships-utils';
|
||||
import { MenuSection } from './menu-section.model';
|
||||
@@ -52,7 +52,7 @@ export class MenuComponent implements OnInit, OnDestroy {
|
||||
*/
|
||||
sectionMap$: BehaviorSubject<Map<string, {
|
||||
injector: Injector,
|
||||
component: GenericConstructor<MenuSectionComponent>
|
||||
component: GenericConstructor<AbstractMenuSectionComponent>
|
||||
}>> = new BehaviorSubject(new Map());
|
||||
|
||||
/**
|
||||
@@ -101,7 +101,7 @@ export class MenuComponent implements OnInit, OnDestroy {
|
||||
}),
|
||||
isNotEmptyOperator(),
|
||||
switchMap((section: MenuSection) => this.getSectionComponent(section).pipe(
|
||||
map((component: GenericConstructor<MenuSectionComponent>) => ({ section, component }))
|
||||
map((component: GenericConstructor<AbstractMenuSectionComponent>) => ({ section, component }))
|
||||
)),
|
||||
distinctUntilChanged((x, y) => x.section.id === y.section.id)
|
||||
).subscribe(({ section, component }) => {
|
||||
@@ -213,9 +213,9 @@ export class MenuComponent implements OnInit, OnDestroy {
|
||||
/**
|
||||
* Retrieve the component for a given MenuSection object
|
||||
* @param {MenuSection} section The given MenuSection
|
||||
* @returns {Observable<GenericConstructor<MenuSectionComponent>>} Emits the constructor of the Component that should be used to render this object
|
||||
* @returns {Observable<GenericConstructor<AbstractMenuSectionComponent>>} Emits the constructor of the Component that should be used to render this object
|
||||
*/
|
||||
private getSectionComponent(section: MenuSection): Observable<GenericConstructor<MenuSectionComponent>> {
|
||||
private getSectionComponent(section: MenuSection): Observable<GenericConstructor<AbstractMenuSectionComponent>> {
|
||||
return this.menuService.hasSubSections(this.menuID, section.id).pipe(
|
||||
map((expandable: boolean) => {
|
||||
return getComponentForMenu(this.menuID, expandable, this.themeService.getThemeName());
|
||||
|
@@ -1,16 +1,14 @@
|
||||
import { MenuSectionComponent } from './menu-section/menu-section.component';
|
||||
import { MenuComponent } from './menu.component';
|
||||
import { NgModule } from '@angular/core';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { RouterModule } from '@angular/router';
|
||||
import { LinkMenuItemComponent } from './menu-item/link-menu-item.component';
|
||||
import { TextMenuItemComponent } from './menu-item/text-menu-item.component';
|
||||
import { OnClickMenuItemComponent } from './menu-item/onclick-menu-item.component';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { NgModule } from '@angular/core';
|
||||
import { RouterModule } from '@angular/router';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { ExternalLinkMenuItemComponent } from './menu-item/external-link-menu-item.component';
|
||||
import { LinkMenuItemComponent } from './menu-item/link-menu-item.component';
|
||||
import { OnClickMenuItemComponent } from './menu-item/onclick-menu-item.component';
|
||||
import { TextMenuItemComponent } from './menu-item/text-menu-item.component';
|
||||
import { MenuComponent } from './menu.component';
|
||||
|
||||
const COMPONENTS = [
|
||||
MenuSectionComponent,
|
||||
MenuComponent,
|
||||
];
|
||||
|
||||
|
@@ -18,7 +18,7 @@ import {
|
||||
} from './menu.actions';
|
||||
import { menusReducer } from './menu.reducer';
|
||||
import { initialMenusState} from './initial-menus-state';
|
||||
import { MenuSectionIndex } from './menu-section-Index.model';
|
||||
import { MenuSectionIndex } from './menu-section-index.model';
|
||||
import { MenuID } from './menu-id.model';
|
||||
|
||||
let visibleSection1;
|
||||
|
@@ -14,7 +14,7 @@ import { initialMenusState} from './initial-menus-state';
|
||||
import { hasValue } from '../empty.util';
|
||||
import { MenusState } from './menus-state.model';
|
||||
import { MenuState } from './menu-state.model';
|
||||
import { MenuSectionIndex } from './menu-section-Index.model';
|
||||
import { MenuSectionIndex } from './menu-section-index.model';
|
||||
import { MenuSections } from './menu-sections.model';
|
||||
import { MenuSection } from './menu-section.model';
|
||||
import { MenuID } from './menu-id.model';
|
||||
|
35
src/app/shared/menu/menu.resolver.ts
Normal file
35
src/app/shared/menu/menu.resolver.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
/**
|
||||
* 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 {
|
||||
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<boolean> {
|
||||
return (
|
||||
route: ActivatedRouteSnapshot,
|
||||
state: RouterStateSnapshot,
|
||||
menuProviderService: MenuProviderService = inject(MenuProviderService),
|
||||
) => menuProviderService.resolveStaticMenu(route, state);
|
||||
}
|
||||
|
||||
export function resolveRouteMenus(...providerTypes: Type<AbstractMenuProvider>[]): (ActivatedRouteSnapshot, RouterStateSnapshot, ProviderMenuService) => Observable<boolean> {
|
||||
// todo: runtime error if undeclared should be compile time ideally
|
||||
return (
|
||||
route: ActivatedRouteSnapshot,
|
||||
state: RouterStateSnapshot,
|
||||
menuProviderService: MenuProviderService = inject(MenuProviderService),
|
||||
) => menuProviderService.resolveRouteMenu(route, state, ...providerTypes);
|
||||
}
|
46
src/app/shared/menu/menu.structure.ts
Normal file
46
src/app/shared/menu/menu.structure.ts
Normal file
@@ -0,0 +1,46 @@
|
||||
/**
|
||||
* 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 {
|
||||
InjectionToken,
|
||||
Provider,
|
||||
Type,
|
||||
} from '@angular/core';
|
||||
import { MenuID } from './menu-id.model';
|
||||
import { AbstractMenuProvider } from './menu-provider';
|
||||
import { MenuProviderService } from './menu-provider.service';
|
||||
|
||||
export const MENU_PROVIDER = new InjectionToken<AbstractMenuProvider>('MENU_PROVIDER');
|
||||
|
||||
type MenuStructure = {
|
||||
[key in MenuID]: Type<AbstractMenuProvider>[];
|
||||
};
|
||||
|
||||
export function buildMenuStructure(structure: MenuStructure): Provider[] {
|
||||
const providers: Provider[] = [
|
||||
MenuProviderService,
|
||||
];
|
||||
|
||||
Object.entries(structure).forEach(([menuID, providerTypes]) => {
|
||||
for (const [index, providerType] of providerTypes.entries()) {
|
||||
// todo: should complain if not injectable!
|
||||
providers.push(providerType);
|
||||
providers.push({
|
||||
provide: MENU_PROVIDER,
|
||||
multi: true,
|
||||
useFactory(provider: AbstractMenuProvider): AbstractMenuProvider {
|
||||
provider.menuID = menuID as MenuID;
|
||||
provider.index = provider.index ?? index;
|
||||
return provider;
|
||||
},
|
||||
deps: [providerType],
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return providers;
|
||||
}
|
94
src/app/shared/menu/providers/access-control.menu.ts
Normal file
94
src/app/shared/menu/providers/access-control.menu.ts
Normal file
@@ -0,0 +1,94 @@
|
||||
/**
|
||||
* 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 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';
|
||||
|
||||
@Injectable()
|
||||
export class AccessControlMenuProvider extends AbstractExpandableMenuProvider {
|
||||
constructor(
|
||||
protected authorizationService: AuthorizationDataService,
|
||||
protected scriptDataService: ScriptDataService,
|
||||
protected modalService: NgbModal,
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
public getTopSection(): Observable<MenuTopSection> {
|
||||
return observableOf({
|
||||
model: {
|
||||
type: MenuItemType.TEXT,
|
||||
text: 'menu.section.access_control',
|
||||
},
|
||||
icon: 'key'
|
||||
});
|
||||
}
|
||||
|
||||
public getSubSections(): Observable<MenuSubSection[]> {
|
||||
return observableCombineLatest([
|
||||
this.authorizationService.isAuthorized(FeatureID.AdministratorOf),
|
||||
this.authorizationService.isAuthorized(FeatureID.CanManageGroups),
|
||||
]).pipe(
|
||||
map(([isSiteAdmin, canManageGroups]) => {
|
||||
return [
|
||||
{
|
||||
visible: isSiteAdmin,
|
||||
model: {
|
||||
type: MenuItemType.LINK,
|
||||
text: 'menu.section.access_control_people',
|
||||
link: '/access-control/epeople',
|
||||
},
|
||||
},
|
||||
{
|
||||
visible: canManageGroups,
|
||||
model: {
|
||||
type: MenuItemType.LINK,
|
||||
text: 'menu.section.access_control_groups',
|
||||
link: '/access-control/groups',
|
||||
},
|
||||
},
|
||||
{
|
||||
visible: isSiteAdmin,
|
||||
model: {
|
||||
type: MenuItemType.LINK,
|
||||
text: 'menu.section.access_control_bulk',
|
||||
link: '/access-control/bulk-access',
|
||||
},
|
||||
},
|
||||
// TODO: enable this menu item once the feature has been implemented
|
||||
// {
|
||||
// id: 'access_control_authorizations',
|
||||
// parentID: 'access_control',
|
||||
// active: false,
|
||||
// visible: authorized,
|
||||
// model: {
|
||||
// type: MenuItemType.LINK,
|
||||
// text: 'menu.section.access_control_authorizations',
|
||||
// link: ''
|
||||
// } as LinkMenuItemModel,
|
||||
// },
|
||||
] as MenuSubSection[];
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
50
src/app/shared/menu/providers/admin-search.menu.ts
Normal file
50
src/app/shared/menu/providers/admin-search.menu.ts
Normal file
@@ -0,0 +1,50 @@
|
||||
/**
|
||||
* 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 {
|
||||
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';
|
||||
|
||||
@Injectable()
|
||||
export class AdminSearchMenuProvider extends AbstractMenuProvider {
|
||||
constructor(
|
||||
protected authorizationService: AuthorizationDataService,
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
public getSections(): Observable<PartialMenuSection[]> {
|
||||
return combineLatest([
|
||||
this.authorizationService.isAuthorized(FeatureID.AdministratorOf),
|
||||
]).pipe(
|
||||
map(([isSiteAdmin]) => {
|
||||
return [
|
||||
{
|
||||
visible: isSiteAdmin,
|
||||
model: {
|
||||
type: MenuItemType.LINK,
|
||||
text: 'menu.section.admin_search',
|
||||
link: '/admin/search',
|
||||
},
|
||||
icon: 'search',
|
||||
},
|
||||
];
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
67
src/app/shared/menu/providers/browse.menu.ts
Normal file
67
src/app/shared/menu/providers/browse.menu.ts
Normal file
@@ -0,0 +1,67 @@
|
||||
/**
|
||||
* 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 {
|
||||
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';
|
||||
import { RemoteData } from '../../../core/data/remote-data';
|
||||
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';
|
||||
|
||||
@Injectable()
|
||||
export class BrowseMenuProvider extends AbstractExpandableMenuProvider {
|
||||
constructor(
|
||||
protected browseService: BrowseService,
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
getTopSection(): Observable<MenuTopSection> {
|
||||
return observableOf(
|
||||
{
|
||||
model: {
|
||||
type: MenuItemType.TEXT,
|
||||
text: 'menu.section.browse_global',
|
||||
} as TextMenuItemModel,
|
||||
icon: 'globe',
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
getSubSections(): Observable<MenuSubSection[]> {
|
||||
return this.browseService.getBrowseDefinitions().pipe(
|
||||
getFirstSucceededRemoteData(),
|
||||
map((rd: RemoteData<PaginatedList<BrowseDefinition>>) => {
|
||||
return [
|
||||
...rd.payload.page.map((browseDef) => {
|
||||
return {
|
||||
visible: true,
|
||||
model: {
|
||||
type: MenuItemType.LINK,
|
||||
text: `menu.section.browse_global_by_${browseDef.id}`,
|
||||
link: `/browse/${browseDef.id}`,
|
||||
},
|
||||
};
|
||||
}),
|
||||
] as MenuSubSection[];
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
65
src/app/shared/menu/providers/comcol-subscribe.menu.ts
Normal file
65
src/app/shared/menu/providers/comcol-subscribe.menu.ts
Normal file
@@ -0,0 +1,65 @@
|
||||
/**
|
||||
* 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,
|
||||
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';
|
||||
|
||||
@Injectable()
|
||||
export class SubscribeMenuProvider extends DSpaceObjectPageMenuProvider<Community | Collection> {
|
||||
constructor(
|
||||
protected authorizationService: AuthorizationDataService,
|
||||
protected modalService: NgbModal,
|
||||
protected dsoDataService: DSpaceObjectDataService,
|
||||
) {
|
||||
super(dsoDataService);
|
||||
}
|
||||
|
||||
protected isApplicable(dso: Community | Collection): boolean {
|
||||
// @ts-ignore
|
||||
return dso.type === COMMUNITY.value || dso.type.value === COLLECTION;
|
||||
}
|
||||
|
||||
public getSectionsForContext(dso: Community | Collection): Observable<PartialMenuSection[]> {
|
||||
return combineLatest([
|
||||
this.authorizationService.isAuthorized(FeatureID.CanSubscribe, dso.self),
|
||||
]).pipe(
|
||||
map(([canSubscribe]) => {
|
||||
return [
|
||||
{
|
||||
visible: canSubscribe,
|
||||
model: {
|
||||
type: MenuItemType.ONCLICK,
|
||||
text: 'subscriptions.tooltip',
|
||||
function: () => {
|
||||
const modalRef = this.modalService.open(SubscriptionModalComponent);
|
||||
modalRef.componentInstance.dso = dso;
|
||||
}
|
||||
} as OnClickMenuItemModel,
|
||||
icon: 'bell',
|
||||
},
|
||||
] as PartialMenuSection[];
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
35
src/app/shared/menu/providers/community-list.menu.ts
Normal file
35
src/app/shared/menu/providers/community-list.menu.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
/**
|
||||
* 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 {
|
||||
Observable,
|
||||
of,
|
||||
} from 'rxjs';
|
||||
import { MenuItemType } from '../menu-item-type.model';
|
||||
import {
|
||||
AbstractMenuProvider,
|
||||
PartialMenuSection,
|
||||
} from '../menu-provider';
|
||||
|
||||
@Injectable()
|
||||
export class CommunityListMenuProvider extends AbstractMenuProvider {
|
||||
public getSections(): Observable<PartialMenuSection[]> {
|
||||
return of([
|
||||
{
|
||||
visible: true,
|
||||
model: {
|
||||
type: MenuItemType.LINK,
|
||||
text: `menu.section.browse_global_communities_and_collections`,
|
||||
link: `/community-list`,
|
||||
},
|
||||
icon: 'diagram-project'
|
||||
},
|
||||
] as PartialMenuSection[]);
|
||||
}
|
||||
}
|
51
src/app/shared/menu/providers/curation.menu.ts
Normal file
51
src/app/shared/menu/providers/curation.menu.ts
Normal file
@@ -0,0 +1,51 @@
|
||||
/**
|
||||
* 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 {
|
||||
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';
|
||||
|
||||
@Injectable()
|
||||
export class CurationMenuProvider extends AbstractMenuProvider {
|
||||
constructor(
|
||||
protected authorizationService: AuthorizationDataService,
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
public getSections(): Observable<PartialMenuSection[]> {
|
||||
return combineLatest([
|
||||
this.authorizationService.isAuthorized(FeatureID.AdministratorOf),
|
||||
]).pipe(
|
||||
map(([isSiteAdmin]) => {
|
||||
return [
|
||||
{
|
||||
visible: isSiteAdmin,
|
||||
model: {
|
||||
type: MenuItemType.LINK,
|
||||
text: 'menu.section.curation_task',
|
||||
link: 'admin/curation-tasks',
|
||||
} as LinkMenuItemModel,
|
||||
icon: 'filter',
|
||||
},
|
||||
] as PartialMenuSection[];
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
53
src/app/shared/menu/providers/dso-edit.menu.ts
Normal file
53
src/app/shared/menu/providers/dso-edit.menu.ts
Normal file
@@ -0,0 +1,53 @@
|
||||
/**
|
||||
* 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 {
|
||||
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';
|
||||
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';
|
||||
|
||||
@Injectable()
|
||||
export class DSpaceObjectEditMenuProvider extends DSpaceObjectPageMenuProvider<DSpaceObject> {
|
||||
constructor(
|
||||
protected authorizationDataService: AuthorizationDataService,
|
||||
protected dsoDataService: DSpaceObjectDataService,
|
||||
) {
|
||||
super(dsoDataService);
|
||||
}
|
||||
|
||||
public getSectionsForContext(dso: DSpaceObject): Observable<PartialMenuSection[]> {
|
||||
return combineLatest([
|
||||
this.authorizationDataService.isAuthorized(FeatureID.CanEditMetadata, dso.self),
|
||||
]).pipe(
|
||||
map(([canEditItem]) => {
|
||||
return [
|
||||
{
|
||||
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',
|
||||
},
|
||||
] as PartialMenuSection[];
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
56
src/app/shared/menu/providers/dso.menu.ts
Normal file
56
src/app/shared/menu/providers/dso.menu.ts
Normal file
@@ -0,0 +1,56 @@
|
||||
/**
|
||||
* 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<T extends DSpaceObject> extends AbstractRouteContextMenuProvider<T> {
|
||||
allRoutes = false;
|
||||
|
||||
protected constructor(
|
||||
protected dsoDataService: DSpaceObjectDataService,
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
|
||||
public getRouteContext(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<T | undefined> {
|
||||
// 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();
|
||||
}
|
||||
}
|
||||
}
|
92
src/app/shared/menu/providers/edit.menu.ts
Normal file
92
src/app/shared/menu/providers/edit.menu.ts
Normal file
@@ -0,0 +1,92 @@
|
||||
/**
|
||||
* 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 { 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';
|
||||
|
||||
@Injectable()
|
||||
export class EditMenuProvider extends AbstractExpandableMenuProvider {
|
||||
constructor(
|
||||
protected authorizationService: AuthorizationDataService,
|
||||
protected modalService: NgbModal,
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
public getTopSection(): Observable<MenuTopSection> {
|
||||
return observableOf(
|
||||
{
|
||||
model: {
|
||||
type: MenuItemType.TEXT,
|
||||
text: 'menu.section.edit',
|
||||
},
|
||||
icon: 'pencil',
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
public getSubSections(): Observable<MenuSubSection[]> {
|
||||
return combineLatest([
|
||||
this.authorizationService.isAuthorized(FeatureID.IsCollectionAdmin),
|
||||
this.authorizationService.isAuthorized(FeatureID.IsCommunityAdmin),
|
||||
this.authorizationService.isAuthorized(FeatureID.CanEditItem),
|
||||
]).pipe(
|
||||
map(([isCollectionAdmin, isCommunityAdmin, canEditItem]) => {
|
||||
return [
|
||||
{
|
||||
visible: isCommunityAdmin,
|
||||
model: {
|
||||
type: MenuItemType.ONCLICK,
|
||||
text: 'menu.section.edit_community',
|
||||
function: () => {
|
||||
this.modalService.open(ThemedEditCommunitySelectorComponent);
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
visible: isCollectionAdmin,
|
||||
model: {
|
||||
type: MenuItemType.ONCLICK,
|
||||
text: 'menu.section.edit_collection',
|
||||
function: () => {
|
||||
this.modalService.open(ThemedEditCollectionSelectorComponent);
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
visible: canEditItem,
|
||||
model: {
|
||||
type: MenuItemType.ONCLICK,
|
||||
text: 'menu.section.edit_item',
|
||||
function: () => {
|
||||
this.modalService.open(ThemedEditItemSelectorComponent);
|
||||
},
|
||||
},
|
||||
},
|
||||
] as MenuSubSection[];
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
64
src/app/shared/menu/providers/expandable-menu-provider.ts
Normal file
64
src/app/shared/menu/providers/expandable-menu-provider.ts
Normal file
@@ -0,0 +1,64 @@
|
||||
/**
|
||||
* 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 { Omit } from '@material-ui/core';
|
||||
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';
|
||||
|
||||
export type MenuTopSection = Omit<PartialMenuSection, 'visible'>;
|
||||
export type MenuSubSection = Omit<PartialMenuSection, 'parentID'>;
|
||||
|
||||
export abstract class AbstractExpandableMenuProvider extends AbstractMenuProvider {
|
||||
protected showWithoutSubsections = false;
|
||||
|
||||
abstract getTopSection(): Observable<MenuTopSection>;
|
||||
|
||||
abstract getSubSections(): Observable<MenuSubSection[]>;
|
||||
|
||||
protected includeSubSections(): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
getSections(): Observable<PartialMenuSection[]> {
|
||||
const full = this.includeSubSections();
|
||||
const parentID = uuidv4();
|
||||
|
||||
return combineLatest([
|
||||
this.getTopSection(),
|
||||
full ? this.getSubSections() : observableOf([]),
|
||||
]).pipe(
|
||||
map((
|
||||
[partialTopSection, partialSubSections]: [MenuTopSection, MenuSubSection[]]
|
||||
) => {
|
||||
const subSections = partialSubSections.map(partialSub => {
|
||||
return {
|
||||
...partialSub,
|
||||
parentID: parentID,
|
||||
};
|
||||
});
|
||||
|
||||
return [
|
||||
...subSections,
|
||||
{
|
||||
...partialTopSection,
|
||||
id: parentID,
|
||||
visible: full ? subSections.some(sub => sub.visible) : this.showWithoutSubsections,
|
||||
},
|
||||
];
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
88
src/app/shared/menu/providers/export.menu.ts
Normal file
88
src/app/shared/menu/providers/export.menu.ts
Normal file
@@ -0,0 +1,88 @@
|
||||
/**
|
||||
* 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 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 { 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';
|
||||
|
||||
@Injectable()
|
||||
export class ExportMenuProvider extends AbstractExpandableMenuProvider {
|
||||
constructor(
|
||||
protected authorizationService: AuthorizationDataService,
|
||||
protected scriptDataService: ScriptDataService,
|
||||
protected modalService: NgbModal,
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
public getTopSection(): Observable<MenuTopSection> {
|
||||
return observableOf(
|
||||
{
|
||||
model: {
|
||||
type: MenuItemType.TEXT,
|
||||
text: 'menu.section.export',
|
||||
},
|
||||
icon: 'file-export',
|
||||
shouldPersistOnRouteChange: true,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
public getSubSections(): Observable<MenuSubSection[]> {
|
||||
return observableCombineLatest([
|
||||
this.authorizationService.isAuthorized(FeatureID.AdministratorOf),
|
||||
this.scriptDataService.scriptWithNameExistsAndCanExecute(METADATA_EXPORT_SCRIPT_NAME),
|
||||
]).pipe(
|
||||
map(([authorized, metadataExportScriptExists]) => {
|
||||
return [
|
||||
{
|
||||
visible: authorized && metadataExportScriptExists,
|
||||
model: {
|
||||
type: MenuItemType.ONCLICK,
|
||||
text: 'menu.section.export_metadata',
|
||||
function: () => {
|
||||
this.modalService.open(ExportMetadataSelectorComponent);
|
||||
},
|
||||
},
|
||||
shouldPersistOnRouteChange: true,
|
||||
},
|
||||
{
|
||||
visible: authorized && metadataExportScriptExists,
|
||||
model: {
|
||||
type: MenuItemType.ONCLICK,
|
||||
text: 'menu.section.export_batch',
|
||||
function: () => {
|
||||
this.modalService.open(ExportBatchSelectorComponent);
|
||||
},
|
||||
},
|
||||
shouldPersistOnRouteChange: true,
|
||||
},
|
||||
] as MenuSubSection[];
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
50
src/app/shared/menu/providers/health.menu.ts
Normal file
50
src/app/shared/menu/providers/health.menu.ts
Normal file
@@ -0,0 +1,50 @@
|
||||
/**
|
||||
* 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 {
|
||||
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';
|
||||
|
||||
@Injectable()
|
||||
export class HealthMenuProvider extends AbstractMenuProvider {
|
||||
constructor(
|
||||
protected authorizationService: AuthorizationDataService,
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
public getSections(): Observable<PartialMenuSection[]> {
|
||||
return combineLatest([
|
||||
this.authorizationService.isAuthorized(FeatureID.AdministratorOf),
|
||||
]).pipe(
|
||||
map(([isSiteAdmin]) => {
|
||||
return [
|
||||
{
|
||||
visible: isSiteAdmin,
|
||||
model: {
|
||||
type: MenuItemType.LINK,
|
||||
text: 'menu.section.health',
|
||||
link: '/health',
|
||||
},
|
||||
icon: 'heartbeat',
|
||||
},
|
||||
] as PartialMenuSection[];
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
80
src/app/shared/menu/providers/import.menu.ts
Normal file
80
src/app/shared/menu/providers/import.menu.ts
Normal file
@@ -0,0 +1,80 @@
|
||||
/**
|
||||
* 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 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 { MenuItemType } from '../menu-item-type.model';
|
||||
import {
|
||||
AbstractExpandableMenuProvider,
|
||||
MenuSubSection,
|
||||
MenuTopSection,
|
||||
} from './expandable-menu-provider';
|
||||
|
||||
@Injectable()
|
||||
export class ImportMenuProvider extends AbstractExpandableMenuProvider {
|
||||
constructor(
|
||||
protected authorizationService: AuthorizationDataService,
|
||||
protected scriptDataService: ScriptDataService,
|
||||
protected modalService: NgbModal,
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
public getTopSection(): Observable<MenuTopSection> {
|
||||
return observableOf(
|
||||
{
|
||||
model: {
|
||||
type: MenuItemType.TEXT,
|
||||
text: 'menu.section.import',
|
||||
},
|
||||
icon: 'file-import',
|
||||
shouldPersistOnRouteChange: true,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
public getSubSections(): Observable<MenuSubSection[]> {
|
||||
return observableCombineLatest([
|
||||
this.authorizationService.isAuthorized(FeatureID.AdministratorOf),
|
||||
this.scriptDataService.scriptWithNameExistsAndCanExecute(METADATA_IMPORT_SCRIPT_NAME),
|
||||
]).pipe(
|
||||
map(([authorized, metadataImportScriptExists]) => {
|
||||
return [
|
||||
{
|
||||
visible: authorized && metadataImportScriptExists,
|
||||
model: {
|
||||
type: MenuItemType.LINK,
|
||||
text: 'menu.section.import_metadata',
|
||||
link: '/admin/metadata-import',
|
||||
},
|
||||
},
|
||||
{
|
||||
visible: authorized && metadataImportScriptExists,
|
||||
model: {
|
||||
type: MenuItemType.LINK,
|
||||
text: 'menu.section.import_batch',
|
||||
link: '/admin/batch-import',
|
||||
},
|
||||
},
|
||||
] as MenuSubSection[];
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
93
src/app/shared/menu/providers/item-claim.menu.ts
Normal file
93
src/app/shared/menu/providers/item-claim.menu.ts
Normal file
@@ -0,0 +1,93 @@
|
||||
/**
|
||||
* 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 { TranslateService } from '@ngx-translate/core';
|
||||
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';
|
||||
import { Item } from '../../../core/shared/item.model';
|
||||
import { isNotEmpty } from '../../empty.util';
|
||||
import { NotificationsService } from '../../notifications/notifications.service';
|
||||
import { MenuID } from '../menu-id.model';
|
||||
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';
|
||||
|
||||
@Injectable()
|
||||
// todo: the "Item-ness" of this class is basically unenforced though...
|
||||
export class ClaimMenuProvider extends DSpaceObjectPageMenuProvider<Item> {
|
||||
constructor(
|
||||
protected authorizationService: AuthorizationDataService,
|
||||
protected menuService: MenuService,
|
||||
protected dsoDataService: DSpaceObjectDataService,
|
||||
protected translate: TranslateService,
|
||||
protected notificationsService: NotificationsService,
|
||||
protected researcherProfileService: ResearcherProfileDataService,
|
||||
protected modalService: NgbModal,
|
||||
) {
|
||||
super(dsoDataService);
|
||||
}
|
||||
|
||||
|
||||
protected isApplicable(item: Item): boolean {
|
||||
return this.getDsoType(item) === 'person';
|
||||
}
|
||||
|
||||
public getSectionsForContext(item: Item): Observable<PartialMenuSection[]> {
|
||||
return combineLatest([
|
||||
this.authorizationService.isAuthorized(FeatureID.CanClaimItem, item.self),
|
||||
]).pipe(
|
||||
map(([canClaimItem]) => {
|
||||
return [
|
||||
{
|
||||
visible: canClaimItem,
|
||||
model: {
|
||||
type: MenuItemType.ONCLICK,
|
||||
text: 'item.page.claim.button',
|
||||
function: () => {
|
||||
this.claimResearcher(item);
|
||||
},
|
||||
} as OnClickMenuItemModel,
|
||||
icon: 'hand-paper',
|
||||
},
|
||||
] as PartialMenuSection[];
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Claim a researcher by creating a profile
|
||||
* Shows notifications and/or hides the menu section on success/error
|
||||
*/
|
||||
protected claimResearcher(item: Item) {
|
||||
this.researcherProfileService.createFromExternalSourceAndReturnRelatedItemId(item.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-' + item.uuid);
|
||||
} else {
|
||||
this.notificationsService.error(
|
||||
this.translate.get('researcherprofile.error.claim.title'),
|
||||
this.translate.get('researcherprofile.error.claim.body'),
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
57
src/app/shared/menu/providers/item-orcid.menu.ts
Normal file
57
src/app/shared/menu/providers/item-orcid.menu.ts
Normal file
@@ -0,0 +1,57 @@
|
||||
/**
|
||||
* 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 {
|
||||
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';
|
||||
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';
|
||||
|
||||
@Injectable()
|
||||
export class OrcidMenuProvider extends DSpaceObjectPageMenuProvider<Item> {
|
||||
constructor(
|
||||
protected authorizationService: AuthorizationDataService,
|
||||
protected dsoDataService: DSpaceObjectDataService,
|
||||
) {
|
||||
super(dsoDataService);
|
||||
}
|
||||
|
||||
protected isApplicable(item: Item): boolean {
|
||||
return this.getDsoType(item) === 'person';
|
||||
}
|
||||
|
||||
public getSectionsForContext(item: Item): Observable<PartialMenuSection[]> {
|
||||
return combineLatest([
|
||||
this.authorizationService.isAuthorized(FeatureID.CanSynchronizeWithORCID, item.self),
|
||||
]).pipe(
|
||||
map(([canSynchronizeWithOrcid]) => {
|
||||
return [
|
||||
{
|
||||
visible: canSynchronizeWithOrcid,
|
||||
model: {
|
||||
type: MenuItemType.LINK,
|
||||
text: 'item.page.orcid.tooltip',
|
||||
link: new URLCombiner(getDSORoute(item), 'orcid').toString()
|
||||
} as LinkMenuItemModel,
|
||||
icon: 'orcid fab fa-lg',
|
||||
},
|
||||
] as PartialMenuSection[];
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
60
src/app/shared/menu/providers/item-versioning.menu.ts
Normal file
60
src/app/shared/menu/providers/item-versioning.menu.ts
Normal file
@@ -0,0 +1,60 @@
|
||||
/**
|
||||
* 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 {
|
||||
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';
|
||||
import { DsoVersioningModalService } from '../../dso-page/dso-versioning-modal-service/dso-versioning-modal.service';
|
||||
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';
|
||||
|
||||
@Injectable()
|
||||
export class VersioningMenuProvider extends DSpaceObjectPageMenuProvider<Item> {
|
||||
constructor(
|
||||
protected authorizationService: AuthorizationDataService,
|
||||
protected dsoVersioningModalService: DsoVersioningModalService,
|
||||
protected dsoDataService: DSpaceObjectDataService,
|
||||
) {
|
||||
super(dsoDataService);
|
||||
}
|
||||
|
||||
public getSectionsForContext(item: Item): Observable<PartialMenuSection[]> {
|
||||
console.log(`VersioningMenuProvider.getSectionsForContext()`, item); // todo: remove this
|
||||
|
||||
return combineLatest([
|
||||
this.authorizationService.isAuthorized(FeatureID.CanCreateVersion, item.self),
|
||||
this.dsoVersioningModalService.isNewVersionButtonDisabled(item),
|
||||
this.dsoVersioningModalService.getVersioningTooltipMessage(item, 'item.page.version.hasDraft', 'item.page.version.create'),
|
||||
]).pipe(
|
||||
map(([canCreateVersion, disableVersioning, versionTooltip]) => {
|
||||
return [
|
||||
{
|
||||
visible: canCreateVersion,
|
||||
model: {
|
||||
type: MenuItemType.ONCLICK,
|
||||
text: versionTooltip,
|
||||
disabled: disableVersioning,
|
||||
function: () => {
|
||||
this.dsoVersioningModalService.openCreateVersionModal(item);
|
||||
},
|
||||
} as OnClickMenuItemModel,
|
||||
icon: 'code-branch',
|
||||
},
|
||||
] as PartialMenuSection[];
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
52
src/app/shared/menu/providers/item.menu.ts
Normal file
52
src/app/shared/menu/providers/item.menu.ts
Normal file
@@ -0,0 +1,52 @@
|
||||
/**
|
||||
* 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<Item> {
|
||||
allRoutes = false;
|
||||
|
||||
protected constructor(
|
||||
protected dsoDataService: DSpaceObjectDataService,
|
||||
@Optional() protected resolver?: ItemPageResolver,
|
||||
) {
|
||||
super(dsoDataService);
|
||||
}
|
||||
|
||||
public getRouteContext(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<Item | undefined> {
|
||||
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<RemoteData<Item>>).pipe(
|
||||
getFirstCompletedRemoteData(),
|
||||
map((dsoRD) => {
|
||||
if (dsoRD.hasSucceeded) {
|
||||
return dsoRD.payload;
|
||||
} else {
|
||||
return undefined;
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
100
src/app/shared/menu/providers/new.menu.ts
Normal file
100
src/app/shared/menu/providers/new.menu.ts
Normal file
@@ -0,0 +1,100 @@
|
||||
/**
|
||||
* 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 { 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';
|
||||
|
||||
@Injectable()
|
||||
export class NewMenuProvider extends AbstractExpandableMenuProvider {
|
||||
constructor(
|
||||
protected authorizationService: AuthorizationDataService,
|
||||
protected modalService: NgbModal,
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
public getTopSection(): Observable<MenuTopSection> {
|
||||
return observableOf(
|
||||
{
|
||||
model: {
|
||||
type: MenuItemType.TEXT,
|
||||
text: 'menu.section.new'
|
||||
} as TextMenuItemModel,
|
||||
icon: 'plus',
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
public getSubSections(): Observable<MenuSubSection[]> {
|
||||
return combineLatest([
|
||||
this.authorizationService.isAuthorized(FeatureID.IsCollectionAdmin),
|
||||
this.authorizationService.isAuthorized(FeatureID.IsCommunityAdmin),
|
||||
this.authorizationService.isAuthorized(FeatureID.AdministratorOf),
|
||||
this.authorizationService.isAuthorized(FeatureID.CanSubmit),
|
||||
]).pipe(map(([isCollectionAdmin, isCommunityAdmin, isSiteAdmin, canSubmit]) => {
|
||||
return [
|
||||
{
|
||||
visible: isCollectionAdmin,
|
||||
model: {
|
||||
type: MenuItemType.ONCLICK,
|
||||
text: 'menu.section.new_community',
|
||||
function: () => {
|
||||
this.modalService.open(ThemedCreateCommunityParentSelectorComponent);
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
visible: isCommunityAdmin,
|
||||
model: {
|
||||
type: MenuItemType.ONCLICK,
|
||||
text: 'menu.section.new_collection',
|
||||
function: () => {
|
||||
this.modalService.open(ThemedCreateCollectionParentSelectorComponent);
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
visible: canSubmit,
|
||||
model: {
|
||||
type: MenuItemType.ONCLICK,
|
||||
text: 'menu.section.new_item',
|
||||
function: () => {
|
||||
this.modalService.open(ThemedCreateItemParentSelectorComponent);
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
visible: isSiteAdmin,
|
||||
model: {
|
||||
type: MenuItemType.LINK,
|
||||
text: 'menu.section.new_process',
|
||||
link: '/processes/new'
|
||||
},
|
||||
},
|
||||
] as MenuSubSection[];
|
||||
}));
|
||||
}
|
||||
}
|
68
src/app/shared/menu/providers/notifications.menu.ts
Normal file
68
src/app/shared/menu/providers/notifications.menu.ts
Normal file
@@ -0,0 +1,68 @@
|
||||
/**
|
||||
* 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<MenuTopSection> {
|
||||
return observableOf(
|
||||
{
|
||||
model: {
|
||||
type: MenuItemType.TEXT,
|
||||
text: 'menu.section.notifications',
|
||||
},
|
||||
icon: 'bell',
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
public getSubSections(): Observable<MenuSubSection[]> {
|
||||
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[];
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
50
src/app/shared/menu/providers/processes.menu.ts
Normal file
50
src/app/shared/menu/providers/processes.menu.ts
Normal file
@@ -0,0 +1,50 @@
|
||||
/**
|
||||
* 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 {
|
||||
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';
|
||||
|
||||
@Injectable()
|
||||
export class ProcessesMenuProvider extends AbstractMenuProvider {
|
||||
constructor(
|
||||
protected authorizationService: AuthorizationDataService,
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
public getSections(): Observable<PartialMenuSection[]> {
|
||||
return combineLatest([
|
||||
this.authorizationService.isAuthorized(FeatureID.AdministratorOf),
|
||||
]).pipe(
|
||||
map(([isSiteAdmin]) => {
|
||||
return [
|
||||
{
|
||||
visible: isSiteAdmin,
|
||||
model: {
|
||||
type: MenuItemType.LINK,
|
||||
text: 'menu.section.processes',
|
||||
link: '/processes',
|
||||
},
|
||||
icon: 'terminal',
|
||||
},
|
||||
] as PartialMenuSection[];
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
75
src/app/shared/menu/providers/registries.menu.ts
Normal file
75
src/app/shared/menu/providers/registries.menu.ts
Normal file
@@ -0,0 +1,75 @@
|
||||
/**
|
||||
* 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 RegistriesMenuProvider extends AbstractExpandableMenuProvider {
|
||||
constructor(
|
||||
protected authorizationService: AuthorizationDataService,
|
||||
protected scriptDataService: ScriptDataService,
|
||||
protected modalService: NgbModal,
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
public getTopSection(): Observable<MenuTopSection> {
|
||||
return observableOf(
|
||||
{
|
||||
model: {
|
||||
type: MenuItemType.TEXT,
|
||||
text: 'menu.section.registries',
|
||||
},
|
||||
icon: 'list',
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
public getSubSections(): Observable<MenuSubSection[]> {
|
||||
return combineLatest([
|
||||
this.authorizationService.isAuthorized(FeatureID.AdministratorOf),
|
||||
]).pipe(
|
||||
map(([authorized]) => {
|
||||
return [
|
||||
{
|
||||
visible: authorized,
|
||||
model: {
|
||||
type: MenuItemType.LINK,
|
||||
text: 'menu.section.registries_metadata',
|
||||
link: 'admin/registries/metadata',
|
||||
},
|
||||
},
|
||||
{
|
||||
visible: authorized,
|
||||
model: {
|
||||
type: MenuItemType.LINK,
|
||||
text: 'menu.section.registries_format',
|
||||
link: 'admin/registries/bitstream-formats',
|
||||
},
|
||||
},
|
||||
] as MenuSubSection[];
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
43
src/app/shared/menu/providers/route-context.menu.ts
Normal file
43
src/app/shared/menu/providers/route-context.menu.ts
Normal file
@@ -0,0 +1,43 @@
|
||||
/**
|
||||
* 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 as observableOf,
|
||||
} from 'rxjs';
|
||||
import { switchMap } from 'rxjs/operators';
|
||||
import { hasValue } from '../../empty.util';
|
||||
import {
|
||||
AbstractMenuProvider,
|
||||
PartialMenuSection,
|
||||
} from '../menu-provider';
|
||||
|
||||
export abstract class AbstractRouteContextMenuProvider<T> extends AbstractMenuProvider {
|
||||
abstract getRouteContext(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<T | undefined>;
|
||||
|
||||
abstract getSectionsForContext(routeContext: T): Observable<PartialMenuSection[]>;
|
||||
|
||||
getSections(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<PartialMenuSection[]> {
|
||||
return this.getRouteContext(route, state).pipe(
|
||||
switchMap((routeContext: T) => {
|
||||
if (hasValue(routeContext) && this.isApplicable(routeContext)) {
|
||||
return this.getSectionsForContext(routeContext);
|
||||
} else {
|
||||
return observableOf([]);
|
||||
}
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
protected isApplicable(routeContext: T): boolean {
|
||||
return true;
|
||||
}
|
||||
}
|
70
src/app/shared/menu/providers/statistics.menu.ts
Normal file
70
src/app/shared/menu/providers/statistics.menu.ts
Normal file
@@ -0,0 +1,70 @@
|
||||
/**
|
||||
* 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 {
|
||||
ActivatedRouteSnapshot,
|
||||
RouterStateSnapshot,
|
||||
} from '@angular/router';
|
||||
import {
|
||||
Observable,
|
||||
of,
|
||||
} from 'rxjs';
|
||||
import { hasNoValue } from '../../empty.util';
|
||||
import { MenuItemType } from '../menu-item-type.model';
|
||||
import { PartialMenuSection } from '../menu-provider';
|
||||
import { AbstractRouteContextMenuProvider } from './route-context.menu';
|
||||
|
||||
interface StatisticsLink {
|
||||
id: string,
|
||||
link: string,
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class StatisticsMenuProvider extends AbstractRouteContextMenuProvider<string> {
|
||||
allRoutes = false;
|
||||
|
||||
public getRouteContext(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<string> {
|
||||
// todo: this won't work for entities!
|
||||
let page = state.url.split('/')[1];
|
||||
const uuid = route.params.id;
|
||||
|
||||
// todo: wow
|
||||
if (page === 'entities') {
|
||||
page = 'items';
|
||||
}
|
||||
|
||||
if (
|
||||
!['home', 'items', 'communities', 'collections'].includes(page) ||
|
||||
(hasNoValue(uuid) && page !== 'home')
|
||||
) {
|
||||
return of(undefined);
|
||||
}
|
||||
|
||||
if (page === 'home') {
|
||||
return of(`statistics`);
|
||||
} else {
|
||||
return of(`statistics/${page}/${uuid}`);
|
||||
}
|
||||
}
|
||||
|
||||
public getSectionsForContext(link: string): Observable<PartialMenuSection[]> {
|
||||
return of([
|
||||
{
|
||||
visible: true,
|
||||
model: {
|
||||
type: MenuItemType.LINK,
|
||||
text: 'menu.section.statistics',
|
||||
link,
|
||||
},
|
||||
icon: 'chart-line',
|
||||
},
|
||||
] as PartialMenuSection[]);
|
||||
}
|
||||
|
||||
}
|
50
src/app/shared/menu/providers/system-wide-alert.menu.ts
Normal file
50
src/app/shared/menu/providers/system-wide-alert.menu.ts
Normal file
@@ -0,0 +1,50 @@
|
||||
/**
|
||||
* 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 {
|
||||
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';
|
||||
|
||||
@Injectable()
|
||||
export class SystemWideAlertMenuProvider extends AbstractMenuProvider {
|
||||
constructor(
|
||||
protected authorizationService: AuthorizationDataService,
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
public getSections(): Observable<PartialMenuSection[]> {
|
||||
return combineLatest([
|
||||
this.authorizationService.isAuthorized(FeatureID.AdministratorOf),
|
||||
]).pipe(
|
||||
map(([isSiteAdmin]) => {
|
||||
return [
|
||||
{
|
||||
visible: isSiteAdmin,
|
||||
model: {
|
||||
type: MenuItemType.LINK,
|
||||
text: 'menu.section.system-wide-alert',
|
||||
link: '/admin/system-wide-alert',
|
||||
},
|
||||
icon: 'exclamation-circle',
|
||||
},
|
||||
] as PartialMenuSection[];
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
50
src/app/shared/menu/providers/workflow.menu.ts
Normal file
50
src/app/shared/menu/providers/workflow.menu.ts
Normal file
@@ -0,0 +1,50 @@
|
||||
/**
|
||||
* 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 {
|
||||
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';
|
||||
|
||||
@Injectable()
|
||||
export class WorkflowMenuProvider extends AbstractMenuProvider {
|
||||
constructor(
|
||||
protected authorizationService: AuthorizationDataService,
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
public getSections(): Observable<PartialMenuSection[]> {
|
||||
return combineLatest([
|
||||
this.authorizationService.isAuthorized(FeatureID.AdministratorOf),
|
||||
]).pipe(
|
||||
map(([isSiteAdmin]) => {
|
||||
return [
|
||||
{
|
||||
visible: isSiteAdmin,
|
||||
model: {
|
||||
type: MenuItemType.LINK,
|
||||
text: 'menu.section.workflow',
|
||||
link: '/admin/workflow',
|
||||
},
|
||||
icon: 'user-check',
|
||||
},
|
||||
] as PartialMenuSection[];
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user