mirror of
https://github.com/DSpace/dspace-angular.git
synced 2025-10-07 10:04:11 +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 { 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 { MenuService } from '../../../shared/menu/menu.service';
|
||||||
import { rendersSectionForMenu } from '../../../shared/menu/menu-section.decorator';
|
import { rendersSectionForMenu } from '../../../shared/menu/menu-section.decorator';
|
||||||
import { LinkMenuItemModel } from '../../../shared/menu/menu-item/models/link.model';
|
import { LinkMenuItemModel } from '../../../shared/menu/menu-item/models/link.model';
|
||||||
@@ -19,7 +19,7 @@ import { Router } from '@angular/router';
|
|||||||
|
|
||||||
})
|
})
|
||||||
@rendersSectionForMenu(MenuID.ADMIN, false)
|
@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
|
* This section resides in the Admin Sidebar
|
||||||
@@ -33,16 +33,17 @@ export class AdminSidebarSectionComponent extends MenuSectionComponent implement
|
|||||||
isDisabled: boolean;
|
isDisabled: boolean;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@Inject('sectionDataProvider') menuSection: MenuSection,
|
@Inject('sectionDataProvider') protected section: MenuSection,
|
||||||
protected menuService: MenuService,
|
protected menuService: MenuService,
|
||||||
protected injector: Injector,
|
protected injector: Injector,
|
||||||
protected router: Router,
|
protected router: Router,
|
||||||
) {
|
) {
|
||||||
super(menuSection, menuService, injector);
|
super(menuService, injector);
|
||||||
this.itemModel = menuSection.model as LinkMenuItemModel;
|
this.itemModel = section.model as LinkMenuItemModel;
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
|
// todo: should support all menu entries?
|
||||||
this.isDisabled = this.itemModel?.disabled || isEmpty(this.itemModel?.link);
|
this.isDisabled = this.itemModel?.disabled || isEmpty(this.itemModel?.link);
|
||||||
super.ngOnInit();
|
super.ngOnInit();
|
||||||
}
|
}
|
||||||
|
@@ -13,7 +13,7 @@
|
|||||||
(keyup.enter)="toggleSection($event)"
|
(keyup.enter)="toggleSection($event)"
|
||||||
>
|
>
|
||||||
<div class="shortcut-icon h-100">
|
<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>
|
||||||
<div class="sidebar-collapsible">
|
<div class="sidebar-collapsible">
|
||||||
<div class="toggle">
|
<div class="toggle">
|
||||||
|
@@ -1,5 +1,6 @@
|
|||||||
import { Component, Inject, Injector, OnInit } from '@angular/core';
|
import { Component, Inject, Injector, OnInit } from '@angular/core';
|
||||||
import { rotate } from '../../../shared/animations/rotate';
|
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 { AdminSidebarSectionComponent } from '../admin-sidebar-section/admin-sidebar-section.component';
|
||||||
import { slide } from '../../../shared/animations/slide';
|
import { slide } from '../../../shared/animations/slide';
|
||||||
import { CSSVariableService } from '../../../shared/sass-helper/css-variable.service';
|
import { CSSVariableService } from '../../../shared/sass-helper/css-variable.service';
|
||||||
@@ -51,13 +52,13 @@ export class ExpandableAdminSidebarSectionComponent extends AdminSidebarSectionC
|
|||||||
expanded: Observable<boolean>;
|
expanded: Observable<boolean>;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@Inject('sectionDataProvider') menuSection,
|
@Inject('sectionDataProvider') protected section: MenuSection,
|
||||||
protected menuService: MenuService,
|
protected menuService: MenuService,
|
||||||
private variableService: CSSVariableService,
|
private variableService: CSSVariableService,
|
||||||
protected injector: Injector,
|
protected injector: Injector,
|
||||||
protected router: Router,
|
protected router: Router,
|
||||||
) {
|
) {
|
||||||
super(menuSection, menuService, injector, router);
|
super(section, menuService, injector, router);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@@ -1,11 +1,8 @@
|
|||||||
import { NgModule } from '@angular/core';
|
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 {
|
import {
|
||||||
SiteAdministratorGuard
|
NoPreloading,
|
||||||
} from './core/data/feature-authorization/feature-authorization-guard/site-administrator.guard';
|
RouterModule,
|
||||||
|
} from '@angular/router';
|
||||||
import {
|
import {
|
||||||
ACCESS_CONTROL_MODULE_PATH,
|
ACCESS_CONTROL_MODULE_PATH,
|
||||||
ADMIN_MODULE_PATH,
|
ADMIN_MODULE_PATH,
|
||||||
@@ -24,22 +21,24 @@ import {
|
|||||||
} from './app-routing-paths';
|
} from './app-routing-paths';
|
||||||
import { COLLECTION_MODULE_PATH } from './collection-page/collection-page-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 { COMMUNITY_MODULE_PATH } from './community-page/community-page-routing-paths';
|
||||||
import { ITEM_MODULE_PATH } from './item-page/item-page-routing-paths';
|
import { AuthBlockingGuard } from './core/auth/auth-blocking.guard';
|
||||||
import { PROCESS_MODULE_PATH } from './process-page/process-page-routing.paths';
|
|
||||||
import { ReloadGuard } from './core/reload/reload.guard';
|
import { AuthenticatedGuard } from './core/auth/authenticated.guard';
|
||||||
import { EndUserAgreementCurrentUserGuard } from './core/end-user-agreement/end-user-agreement-current-user.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 { SiteRegisterGuard } from './core/data/feature-authorization/feature-authorization-guard/site-register.guard';
|
||||||
import { ThemedPageNotFoundComponent } from './pagenotfound/themed-pagenotfound.component';
|
import { EndUserAgreementCurrentUserGuard } from './core/end-user-agreement/end-user-agreement-current-user.guard';
|
||||||
import { ThemedForbiddenComponent } from './forbidden/themed-forbidden.component';
|
import { ReloadGuard } from './core/reload/reload.guard';
|
||||||
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 { ServerCheckGuard } from './core/server-check/server-check.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 { MenuResolver } from './menu.resolver';
|
||||||
import { ThemedPageErrorComponent } from './page-error/themed-page-error.component';
|
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({
|
@NgModule({
|
||||||
imports: [
|
imports: [
|
||||||
@@ -50,7 +49,10 @@ import { ThemedPageErrorComponent } from './page-error/themed-page-error.compone
|
|||||||
path: '',
|
path: '',
|
||||||
canActivate: [AuthBlockingGuard],
|
canActivate: [AuthBlockingGuard],
|
||||||
canActivateChild: [ServerCheckGuard],
|
canActivateChild: [ServerCheckGuard],
|
||||||
resolve: [MenuResolver],
|
resolve: [
|
||||||
|
resolveStaticMenus(),
|
||||||
|
// MenuResolver,
|
||||||
|
],
|
||||||
children: [
|
children: [
|
||||||
{ path: '', redirectTo: '/home', pathMatch: 'full' },
|
{ 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 {
|
||||||
import { HTTP_INTERCEPTORS, HttpClientModule } from '@angular/common/http';
|
APP_BASE_HREF,
|
||||||
|
CommonModule,
|
||||||
|
DOCUMENT,
|
||||||
|
} from '@angular/common';
|
||||||
|
import {
|
||||||
|
HTTP_INTERCEPTORS,
|
||||||
|
HttpClientModule,
|
||||||
|
} from '@angular/common/http';
|
||||||
import { NgModule } from '@angular/core';
|
import { NgModule } from '@angular/core';
|
||||||
import { BrowserModule } from '@angular/platform-browser';
|
import { BrowserModule } from '@angular/platform-browser';
|
||||||
|
|
||||||
import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
|
import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
|
||||||
|
import { DYNAMIC_MATCHER_PROVIDERS } from '@ng-dynamic-forms/core';
|
||||||
import { EffectsModule } from '@ngrx/effects';
|
import { EffectsModule } from '@ngrx/effects';
|
||||||
import { RouterStateSerializer, StoreRouterConnectingModule } from '@ngrx/router-store';
|
import {
|
||||||
import { MetaReducer, StoreModule, USER_PROVIDED_META_REDUCERS } from '@ngrx/store';
|
RouterStateSerializer,
|
||||||
|
StoreRouterConnectingModule,
|
||||||
|
} from '@ngrx/router-store';
|
||||||
|
import {
|
||||||
|
MetaReducer,
|
||||||
|
StoreModule,
|
||||||
|
USER_PROVIDED_META_REDUCERS,
|
||||||
|
} from '@ngrx/store';
|
||||||
import { TranslateModule } from '@ngx-translate/core';
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
import { ScrollToModule } from '@nicky-lenaers/ngx-scroll-to';
|
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 { AppRoutingModule } from './app-routing.module';
|
||||||
import { AppComponent } from './app.component';
|
import { AppComponent } from './app.component';
|
||||||
import { appEffects } from './app.effects';
|
import { appEffects } from './app.effects';
|
||||||
import { appMetaReducers, debugMetaReducers } from './app.metareducers';
|
import { MENUS } from './app.menus';
|
||||||
import { appReducers, AppState, storeModuleConfig } from './app.reducer';
|
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 { 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 { ClientCookieService } from './core/services/client-cookie.service';
|
||||||
|
import { XsrfInterceptor } from './core/xsrf/xsrf.interceptor';
|
||||||
import { NavbarModule } from './navbar/navbar.module';
|
import { NavbarModule } from './navbar/navbar.module';
|
||||||
|
import { RootModule } from './root.module';
|
||||||
import { DSpaceRouterStateSerializer } from './shared/ngrx/dspace-router-state-serializer';
|
import { DSpaceRouterStateSerializer } from './shared/ngrx/dspace-router-state-serializer';
|
||||||
import { SharedModule } from './shared/shared.module';
|
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() {
|
export function getConfig() {
|
||||||
return environment;
|
return environment;
|
||||||
@@ -105,6 +130,9 @@ const PROVIDERS = [
|
|||||||
},
|
},
|
||||||
// register the dynamic matcher used by form. MUST be provided by the app module
|
// register the dynamic matcher used by form. MUST be provided by the app module
|
||||||
...DYNAMIC_MATCHER_PROVIDERS,
|
...DYNAMIC_MATCHER_PROVIDERS,
|
||||||
|
|
||||||
|
// DI-composable menus
|
||||||
|
...MENUS,
|
||||||
];
|
];
|
||||||
|
|
||||||
const DECLARATIONS = [
|
const DECLARATIONS = [
|
||||||
|
@@ -1,27 +1,28 @@
|
|||||||
import { NgModule } from '@angular/core';
|
import { NgModule } from '@angular/core';
|
||||||
import { RouterModule } from '@angular/router';
|
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 { CollectionPageResolver } from './collection-page.resolver';
|
||||||
import { CreateCollectionPageComponent } from './create-collection-page/create-collection-page.component';
|
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 { CreateCollectionPageGuard } from './create-collection-page/create-collection-page.guard';
|
||||||
import { DeleteCollectionPageComponent } from './delete-collection-page/delete-collection-page.component';
|
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 { ItemTemplatePageResolver } from './edit-item-template-page/item-template-page.resolver';
|
||||||
import { CollectionBreadcrumbResolver } from '../core/breadcrumbs/collection-breadcrumb.resolver';
|
import { ThemedEditItemTemplatePageComponent } from './edit-item-template-page/themed-edit-item-template-page.component';
|
||||||
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 { ThemedCollectionPageComponent } from './themed-collection-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({
|
@NgModule({
|
||||||
imports: [
|
imports: [
|
||||||
@@ -36,7 +37,11 @@ import { DSOEditMenuResolver } from '../shared/dso-page/dso-edit-menu.resolver';
|
|||||||
resolve: {
|
resolve: {
|
||||||
dso: CollectionPageResolver,
|
dso: CollectionPageResolver,
|
||||||
breadcrumb: CollectionBreadcrumbResolver,
|
breadcrumb: CollectionBreadcrumbResolver,
|
||||||
menu: DSOEditMenuResolver
|
menu: resolveRouteMenus(
|
||||||
|
StatisticsMenuProvider,
|
||||||
|
DSpaceObjectEditMenuProvider,
|
||||||
|
SubscribeMenuProvider,
|
||||||
|
),
|
||||||
},
|
},
|
||||||
runGuardsAndResolvers: 'always',
|
runGuardsAndResolvers: 'always',
|
||||||
children: [
|
children: [
|
||||||
@@ -68,21 +73,6 @@ import { DSOEditMenuResolver } from '../shared/dso-page/dso-edit-menu.resolver';
|
|||||||
pathMatch: 'full',
|
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 { NgModule } from '@angular/core';
|
||||||
import { RouterModule } from '@angular/router';
|
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 { 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 { CommunityBreadcrumbResolver } from '../core/breadcrumbs/community-breadcrumb.resolver';
|
||||||
import { DSOBreadcrumbsService } from '../core/breadcrumbs/dso-breadcrumbs.service';
|
import { DSOBreadcrumbsService } from '../core/breadcrumbs/dso-breadcrumbs.service';
|
||||||
import { LinkService } from '../core/cache/builders/link.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 { 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({
|
@NgModule({
|
||||||
imports: [
|
imports: [
|
||||||
@@ -29,7 +34,12 @@ import { DSOEditMenuResolver } from '../shared/dso-page/dso-edit-menu.resolver';
|
|||||||
resolve: {
|
resolve: {
|
||||||
dso: CommunityPageResolver,
|
dso: CommunityPageResolver,
|
||||||
breadcrumb: CommunityBreadcrumbResolver,
|
breadcrumb: CommunityBreadcrumbResolver,
|
||||||
menu: DSOEditMenuResolver
|
// menu: DSOEditMenuResolver,
|
||||||
|
menu: resolveRouteMenus(
|
||||||
|
StatisticsMenuProvider,
|
||||||
|
DSpaceObjectEditMenuProvider,
|
||||||
|
SubscribeMenuProvider,
|
||||||
|
),
|
||||||
},
|
},
|
||||||
runGuardsAndResolvers: 'always',
|
runGuardsAndResolvers: 'always',
|
||||||
children: [
|
children: [
|
||||||
@@ -51,21 +61,6 @@ import { DSOEditMenuResolver } from '../shared/dso-page/dso-edit-menu.resolver';
|
|||||||
pathMatch: 'full',
|
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 { NgModule } from '@angular/core';
|
||||||
import { RouterModule } from '@angular/router';
|
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 { HomePageResolver } from './home-page.resolver';
|
||||||
import { LinkMenuItemModel } from '../shared/menu/menu-item/models/link.model';
|
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',
|
pathMatch: 'full',
|
||||||
data: {
|
data: {
|
||||||
title: 'home.title',
|
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: {
|
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 { NgModule } from '@angular/core';
|
||||||
import { RouterModule } from '@angular/router';
|
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 { 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 { OrcidPageComponent } from './orcid-page/orcid-page.component';
|
||||||
import { OrcidPageGuard } from './orcid-page/orcid-page.guard';
|
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({
|
@NgModule({
|
||||||
imports: [
|
imports: [
|
||||||
@@ -28,7 +35,13 @@ import { DSOEditMenuResolver } from '../shared/dso-page/dso-edit-menu.resolver';
|
|||||||
resolve: {
|
resolve: {
|
||||||
dso: ItemPageResolver,
|
dso: ItemPageResolver,
|
||||||
breadcrumb: ItemBreadcrumbResolver,
|
breadcrumb: ItemBreadcrumbResolver,
|
||||||
menu: DSOEditMenuResolver
|
menu: resolveRouteMenus(
|
||||||
|
StatisticsMenuProvider,
|
||||||
|
OrcidMenuProvider,
|
||||||
|
DSpaceObjectEditMenuProvider,
|
||||||
|
ClaimMenuProvider,
|
||||||
|
VersioningMenuProvider,
|
||||||
|
),
|
||||||
},
|
},
|
||||||
runGuardsAndResolvers: 'always',
|
runGuardsAndResolvers: 'always',
|
||||||
children: [
|
children: [
|
||||||
@@ -61,21 +74,6 @@ import { DSOEditMenuResolver } from '../shared/dso-page/dso-edit-menu.resolver';
|
|||||||
canActivate: [AuthenticatedGuard, OrcidPageGuard]
|
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',
|
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
|
* This class represents a resolver that requests a specific item before the route is activated and will redirect to the
|
||||||
* entity page
|
* entity page
|
||||||
*/
|
*/
|
||||||
@Injectable()
|
@Injectable({
|
||||||
|
providedIn: 'root',
|
||||||
|
})
|
||||||
export class ItemPageResolver extends ItemResolver {
|
export class ItemPageResolver extends ItemResolver {
|
||||||
constructor(
|
constructor(
|
||||||
protected itemService: ItemDataService,
|
protected itemService: ItemDataService,
|
||||||
|
@@ -1,4 +1,5 @@
|
|||||||
import { Component, Inject, Injector, OnInit } from '@angular/core';
|
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 { NavbarSectionComponent } from '../navbar-section/navbar-section.component';
|
||||||
import { MenuService } from '../../shared/menu/menu.service';
|
import { MenuService } from '../../shared/menu/menu.service';
|
||||||
import { slide } from '../../shared/animations/slide';
|
import { slide } from '../../shared/animations/slide';
|
||||||
@@ -23,12 +24,13 @@ export class ExpandableNavbarSectionComponent extends NavbarSectionComponent imp
|
|||||||
*/
|
*/
|
||||||
menuID = MenuID.PUBLIC;
|
menuID = MenuID.PUBLIC;
|
||||||
|
|
||||||
constructor(@Inject('sectionDataProvider') menuSection,
|
constructor(
|
||||||
|
@Inject('sectionDataProvider') protected section: MenuSection,
|
||||||
protected menuService: MenuService,
|
protected menuService: MenuService,
|
||||||
protected injector: Injector,
|
protected injector: Injector,
|
||||||
private windowService: HostWindowService
|
private windowService: HostWindowService,
|
||||||
) {
|
) {
|
||||||
super(menuSection, menuService, injector);
|
super(section, menuService, injector);
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
|
@@ -1,5 +1,6 @@
|
|||||||
import { Component, Inject, Injector, OnInit } from '@angular/core';
|
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 { MenuService } from '../../shared/menu/menu.service';
|
||||||
import { rendersSectionForMenu } from '../../shared/menu/menu-section.decorator';
|
import { rendersSectionForMenu } from '../../shared/menu/menu-section.decorator';
|
||||||
import { MenuID } from '../../shared/menu/menu-id.model';
|
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']
|
styleUrls: ['./navbar-section.component.scss']
|
||||||
})
|
})
|
||||||
@rendersSectionForMenu(MenuID.PUBLIC, false)
|
@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
|
* This section resides in the Public Navbar
|
||||||
*/
|
*/
|
||||||
menuID = MenuID.PUBLIC;
|
menuID = MenuID.PUBLIC;
|
||||||
|
|
||||||
constructor(@Inject('sectionDataProvider') menuSection,
|
constructor(
|
||||||
|
@Inject('sectionDataProvider') protected section: MenuSection,
|
||||||
protected menuService: MenuService,
|
protected menuService: MenuService,
|
||||||
protected injector: Injector
|
protected injector: Injector,
|
||||||
) {
|
) {
|
||||||
super(menuSection, menuService, injector);
|
super(menuService, injector);
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
|
@@ -3,9 +3,21 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.dso-button-menu {
|
.dso-button-menu {
|
||||||
|
// todo: random thought to make dso page dropdown buttons clear
|
||||||
.dropdown-toggle::after {
|
.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 {
|
ul.dropdown-menu {
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
import { Component, Inject, Injector } from '@angular/core';
|
import { Component, Inject, Injector } from '@angular/core';
|
||||||
import { rendersSectionForMenu } from 'src/app/shared/menu/menu-section.decorator';
|
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 { MenuService } from '../../../menu/menu.service';
|
||||||
import { Router } from '@angular/router';
|
import { Router } from '@angular/router';
|
||||||
import { MenuID } from 'src/app/shared/menu/menu-id.model';
|
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'],
|
styleUrls: ['./dso-edit-menu-expandable-section.component.scss'],
|
||||||
})
|
})
|
||||||
@rendersSectionForMenu(MenuID.DSO_EDIT, true)
|
@rendersSectionForMenu(MenuID.DSO_EDIT, true)
|
||||||
export class DsoEditMenuExpandableSectionComponent extends MenuSectionComponent {
|
export class DsoEditMenuExpandableSectionComponent extends AbstractMenuSectionComponent {
|
||||||
|
|
||||||
menuID: MenuID = MenuID.DSO_EDIT;
|
menuID: MenuID = MenuID.DSO_EDIT;
|
||||||
itemModel;
|
itemModel;
|
||||||
@@ -27,13 +27,13 @@ export class DsoEditMenuExpandableSectionComponent extends MenuSectionComponent
|
|||||||
renderIcons$: Observable<boolean>;
|
renderIcons$: Observable<boolean>;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@Inject('sectionDataProvider') menuSection: MenuSection,
|
@Inject('sectionDataProvider') protected section: MenuSection,
|
||||||
protected menuService: MenuService,
|
protected menuService: MenuService,
|
||||||
protected injector: Injector,
|
protected injector: Injector,
|
||||||
protected router: Router,
|
protected router: Router,
|
||||||
) {
|
) {
|
||||||
super(menuSection, menuService, injector);
|
super(menuService, injector);
|
||||||
this.itemModel = menuSection.model;
|
this.itemModel = section.model;
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
import { Component, Inject, Injector, OnInit } from '@angular/core';
|
import { Component, Inject, Injector, OnInit } from '@angular/core';
|
||||||
import { rendersSectionForMenu } from 'src/app/shared/menu/menu-section.decorator';
|
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 { MenuService } from '../../../menu/menu.service';
|
||||||
import { isNotEmpty } from '../../../empty.util';
|
import { isNotEmpty } from '../../../empty.util';
|
||||||
import { MenuID } from '../../../menu/menu-id.model';
|
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']
|
styleUrls: ['./dso-edit-menu-section.component.scss']
|
||||||
})
|
})
|
||||||
@rendersSectionForMenu(MenuID.DSO_EDIT, false)
|
@rendersSectionForMenu(MenuID.DSO_EDIT, false)
|
||||||
export class DsoEditMenuSectionComponent extends MenuSectionComponent implements OnInit {
|
export class DsoEditMenuSectionComponent extends AbstractMenuSectionComponent implements OnInit {
|
||||||
|
|
||||||
menuID: MenuID = MenuID.DSO_EDIT;
|
menuID: MenuID = MenuID.DSO_EDIT;
|
||||||
itemModel;
|
itemModel;
|
||||||
@@ -24,12 +24,12 @@ export class DsoEditMenuSectionComponent extends MenuSectionComponent implements
|
|||||||
canActivate: boolean;
|
canActivate: boolean;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@Inject('sectionDataProvider') menuSection: MenuSection,
|
@Inject('sectionDataProvider') protected section: MenuSection,
|
||||||
protected menuService: MenuService,
|
protected menuService: MenuService,
|
||||||
protected injector: Injector,
|
protected injector: Injector,
|
||||||
) {
|
) {
|
||||||
super(menuSection, menuService, injector);
|
super(menuService, injector);
|
||||||
this.itemModel = menuSection.model;
|
this.itemModel = section.model;
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
|
@@ -6,6 +6,6 @@ import { MenuItemType } from '../../menu-item-type.model';
|
|||||||
*/
|
*/
|
||||||
export class AltmetricMenuItemModel implements MenuItemModel {
|
export class AltmetricMenuItemModel implements MenuItemModel {
|
||||||
type = MenuItemType.ALTMETRIC;
|
type = MenuItemType.ALTMETRIC;
|
||||||
disabled: boolean;
|
disabled?: boolean;
|
||||||
url: string;
|
url: string;
|
||||||
}
|
}
|
||||||
|
@@ -6,7 +6,7 @@ import { MenuItemType } from '../../menu-item-type.model';
|
|||||||
*/
|
*/
|
||||||
export class ExternalLinkMenuItemModel implements MenuItemModel {
|
export class ExternalLinkMenuItemModel implements MenuItemModel {
|
||||||
type = MenuItemType.EXTERNAL;
|
type = MenuItemType.EXTERNAL;
|
||||||
disabled: boolean;
|
disabled?: boolean;
|
||||||
text: string;
|
text: string;
|
||||||
href: string;
|
href: string;
|
||||||
}
|
}
|
||||||
|
@@ -7,7 +7,7 @@ import { Params } from '@angular/router';
|
|||||||
*/
|
*/
|
||||||
export class LinkMenuItemModel implements MenuItemModel {
|
export class LinkMenuItemModel implements MenuItemModel {
|
||||||
type = MenuItemType.LINK;
|
type = MenuItemType.LINK;
|
||||||
disabled: boolean;
|
disabled?: boolean;
|
||||||
text: string;
|
text: string;
|
||||||
link: string;
|
link: string;
|
||||||
queryParams?: Params | null;
|
queryParams?: Params | null;
|
||||||
|
@@ -5,5 +5,5 @@ import { MenuItemType } from '../../menu-item-type.model';
|
|||||||
*/
|
*/
|
||||||
export interface MenuItemModel {
|
export interface MenuItemModel {
|
||||||
type: MenuItemType;
|
type: MenuItemType;
|
||||||
disabled: boolean;
|
disabled?: boolean;
|
||||||
}
|
}
|
||||||
|
@@ -6,7 +6,7 @@ import { MenuItemType } from '../../menu-item-type.model';
|
|||||||
*/
|
*/
|
||||||
export class OnClickMenuItemModel implements MenuItemModel {
|
export class OnClickMenuItemModel implements MenuItemModel {
|
||||||
type = MenuItemType.ONCLICK;
|
type = MenuItemType.ONCLICK;
|
||||||
disabled: boolean;
|
disabled?: boolean;
|
||||||
text: string;
|
text: string;
|
||||||
function: () => {};
|
function: () => void;
|
||||||
}
|
}
|
||||||
|
@@ -1,12 +1,12 @@
|
|||||||
import { MenuItemModel } from './menu-item.model';
|
|
||||||
import { MenuItemType } from '../../menu-item-type.model';
|
import { MenuItemType } from '../../menu-item-type.model';
|
||||||
|
import { MenuItemModel } from './menu-item.model';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Model representing an Search Bar Menu Section
|
* Model representing an Search Bar Menu Section
|
||||||
*/
|
*/
|
||||||
export class SearchMenuItemModel implements MenuItemModel {
|
export class SearchMenuItemModel implements MenuItemModel {
|
||||||
type = MenuItemType.SEARCH;
|
type = MenuItemType.SEARCH;
|
||||||
disabled: boolean;
|
disabled?: boolean;
|
||||||
placeholder: string;
|
placeholder: string;
|
||||||
action: string;
|
action: string;
|
||||||
}
|
}
|
||||||
|
@@ -6,6 +6,6 @@ import { MenuItemType } from '../../menu-item-type.model';
|
|||||||
*/
|
*/
|
||||||
export class TextMenuItemModel implements MenuItemModel {
|
export class TextMenuItemModel implements MenuItemModel {
|
||||||
type = MenuItemType.TEXT;
|
type = MenuItemType.TEXT;
|
||||||
disabled: boolean;
|
disabled?: boolean;
|
||||||
text: string;
|
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';
|
||||||
|
|
||||||
/**
|
export type MenuItemModels =
|
||||||
* Represents the state of a single menu section in the store
|
LinkMenuItemModel
|
||||||
*/
|
| AltmetricMenuItemModel
|
||||||
export class MenuSection {
|
| ExternalLinkMenuItemModel
|
||||||
id: string;
|
| OnClickMenuItemModel
|
||||||
parentID?: string;
|
| SearchMenuItemModel
|
||||||
visible: boolean;
|
| TextMenuItemModel;
|
||||||
active: boolean;
|
|
||||||
model: MenuItemModel;
|
function itemModelFactory(type: MenuItemType): MenuItemModels {
|
||||||
index?: number;
|
switch (type) {
|
||||||
icon?: string;
|
case MenuItemType.TEXT:
|
||||||
shouldPersistOnRouteChange? = false;
|
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 {
|
||||||
import { MenuService } from '../menu.service';
|
Component,
|
||||||
import { getComponentForMenuItemType } from '../menu-item.decorator';
|
Injector,
|
||||||
import { hasNoValue, hasValue } from '../../empty.util';
|
OnDestroy,
|
||||||
import { BehaviorSubject, Observable, Subscription } from 'rxjs';
|
OnInit,
|
||||||
import { MenuItemModel } from '../menu-item/models/menu-item.model';
|
} from '@angular/core';
|
||||||
import { distinctUntilChanged, switchMap } from 'rxjs/operators';
|
import {
|
||||||
|
BehaviorSubject,
|
||||||
|
Observable,
|
||||||
|
Subscription,
|
||||||
|
} from 'rxjs';
|
||||||
|
import {
|
||||||
|
distinctUntilChanged,
|
||||||
|
switchMap,
|
||||||
|
} from 'rxjs/operators';
|
||||||
import { GenericConstructor } from '../../../core/shared/generic-constructor';
|
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 { MenuID } from '../menu-id.model';
|
||||||
import { MenuItemType } from '../menu-item-type.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
|
* A basic implementation of a menu section's component
|
||||||
@@ -17,7 +32,8 @@ import { MenuItemType } from '../menu-item-type.model';
|
|||||||
selector: 'ds-menu-section',
|
selector: 'ds-menu-section',
|
||||||
template: ''
|
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
|
* 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, {
|
sectionMap$: BehaviorSubject<Map<string, {
|
||||||
injector: Injector,
|
injector: Injector,
|
||||||
component: GenericConstructor<MenuSectionComponent>
|
component: GenericConstructor<AbstractMenuSectionComponent>
|
||||||
}>> = new BehaviorSubject(new Map());
|
}>> = new BehaviorSubject(new Map());
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -48,7 +64,10 @@ export class MenuSectionComponent implements OnInit, OnDestroy {
|
|||||||
*/
|
*/
|
||||||
subs: Subscription[] = [];
|
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 { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
|
||||||
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
|
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
|
||||||
|
import { Router } from '@angular/router';
|
||||||
import { TranslateModule } from '@ngx-translate/core';
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
import { ChangeDetectionStrategy, Injector, NO_ERRORS_SCHEMA } from '@angular/core';
|
import {
|
||||||
import { MenuSectionComponent } from './menu-section.component';
|
ChangeDetectionStrategy,
|
||||||
|
Component,
|
||||||
|
Inject,
|
||||||
|
Injector,
|
||||||
|
NO_ERRORS_SCHEMA,
|
||||||
|
} from '@angular/core';
|
||||||
|
import { AbstractMenuSectionComponent } from './abstract-menu-section.component';
|
||||||
import { MenuService } from '../menu.service';
|
import { MenuService } from '../menu.service';
|
||||||
import { MenuServiceStub } from '../../testing/menu-service.stub';
|
import { MenuServiceStub } from '../../testing/menu-service.stub';
|
||||||
import { of as observableOf } from 'rxjs';
|
import { of as observableOf } from 'rxjs';
|
||||||
import { LinkMenuItemComponent } from '../menu-item/link-menu-item.component';
|
import { LinkMenuItemComponent } from '../menu-item/link-menu-item.component';
|
||||||
import { MenuSection } from '../menu-section.model';
|
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', () => {
|
describe('MenuSectionComponent', () => {
|
||||||
let comp: MenuSectionComponent;
|
let comp: AbstractMenuSectionComponent;
|
||||||
let fixture: ComponentFixture<MenuSectionComponent>;
|
let fixture: ComponentFixture<AbstractMenuSectionComponent>;
|
||||||
let menuService: MenuService;
|
let menuService: MenuService;
|
||||||
let dummySection;
|
let dummySection;
|
||||||
|
|
||||||
@@ -23,20 +44,20 @@ describe('MenuSectionComponent', () => {
|
|||||||
} as any;
|
} as any;
|
||||||
TestBed.configureTestingModule({
|
TestBed.configureTestingModule({
|
||||||
imports: [TranslateModule.forRoot(), NoopAnimationsModule],
|
imports: [TranslateModule.forRoot(), NoopAnimationsModule],
|
||||||
declarations: [MenuSectionComponent],
|
declarations: [AbstractMenuSectionComponent],
|
||||||
providers: [
|
providers: [
|
||||||
{ provide: Injector, useValue: {} },
|
{ provide: Injector, useValue: {} },
|
||||||
{ provide: MenuService, useClass: MenuServiceStub },
|
{ provide: MenuService, useClass: MenuServiceStub },
|
||||||
{ provide: MenuSection, useValue: dummySection },
|
{ provide: 'sectionDataProvider', useValue: dummySection },
|
||||||
],
|
],
|
||||||
schemas: [NO_ERRORS_SCHEMA]
|
schemas: [NO_ERRORS_SCHEMA]
|
||||||
}).overrideComponent(MenuSectionComponent, {
|
}).overrideComponent(SomeMenuSectionComponent, {
|
||||||
set: { changeDetection: ChangeDetectionStrategy.Default }
|
set: { changeDetection: ChangeDetectionStrategy.Default }
|
||||||
}).compileComponents();
|
}).compileComponents();
|
||||||
}));
|
}));
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
fixture = TestBed.createComponent(MenuSectionComponent);
|
fixture = TestBed.createComponent(SomeMenuSectionComponent);
|
||||||
comp = fixture.componentInstance;
|
comp = fixture.componentInstance;
|
||||||
menuService = (comp as any).menuService;
|
menuService = (comp as any).menuService;
|
||||||
spyOn(comp as any, 'getMenuItemComponent').and.returnValue(LinkMenuItemComponent);
|
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 { MenuSections } from './menu-sections.model';
|
||||||
import { MenuID } from './menu-id.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 { distinctUntilChanged, map, mergeMap, switchMap } from 'rxjs/operators';
|
||||||
import { GenericConstructor } from '../../core/shared/generic-constructor';
|
import { GenericConstructor } from '../../core/shared/generic-constructor';
|
||||||
import { hasValue, isNotEmptyOperator } from '../empty.util';
|
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 { getComponentForMenu } from './menu-section.decorator';
|
||||||
import { compareArraysUsingIds } from '../../item-page/simple/item-types/shared/item-relationships-utils';
|
import { compareArraysUsingIds } from '../../item-page/simple/item-types/shared/item-relationships-utils';
|
||||||
import { MenuSection } from './menu-section.model';
|
import { MenuSection } from './menu-section.model';
|
||||||
@@ -52,7 +52,7 @@ export class MenuComponent implements OnInit, OnDestroy {
|
|||||||
*/
|
*/
|
||||||
sectionMap$: BehaviorSubject<Map<string, {
|
sectionMap$: BehaviorSubject<Map<string, {
|
||||||
injector: Injector,
|
injector: Injector,
|
||||||
component: GenericConstructor<MenuSectionComponent>
|
component: GenericConstructor<AbstractMenuSectionComponent>
|
||||||
}>> = new BehaviorSubject(new Map());
|
}>> = new BehaviorSubject(new Map());
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -101,7 +101,7 @@ export class MenuComponent implements OnInit, OnDestroy {
|
|||||||
}),
|
}),
|
||||||
isNotEmptyOperator(),
|
isNotEmptyOperator(),
|
||||||
switchMap((section: MenuSection) => this.getSectionComponent(section).pipe(
|
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)
|
distinctUntilChanged((x, y) => x.section.id === y.section.id)
|
||||||
).subscribe(({ section, component }) => {
|
).subscribe(({ section, component }) => {
|
||||||
@@ -213,9 +213,9 @@ export class MenuComponent implements OnInit, OnDestroy {
|
|||||||
/**
|
/**
|
||||||
* Retrieve the component for a given MenuSection object
|
* Retrieve the component for a given MenuSection object
|
||||||
* @param {MenuSection} section The given MenuSection
|
* @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(
|
return this.menuService.hasSubSections(this.menuID, section.id).pipe(
|
||||||
map((expandable: boolean) => {
|
map((expandable: boolean) => {
|
||||||
return getComponentForMenu(this.menuID, expandable, this.themeService.getThemeName());
|
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 { 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 { 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 = [
|
const COMPONENTS = [
|
||||||
MenuSectionComponent,
|
|
||||||
MenuComponent,
|
MenuComponent,
|
||||||
];
|
];
|
||||||
|
|
||||||
|
@@ -18,7 +18,7 @@ import {
|
|||||||
} from './menu.actions';
|
} from './menu.actions';
|
||||||
import { menusReducer } from './menu.reducer';
|
import { menusReducer } from './menu.reducer';
|
||||||
import { initialMenusState} from './initial-menus-state';
|
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';
|
import { MenuID } from './menu-id.model';
|
||||||
|
|
||||||
let visibleSection1;
|
let visibleSection1;
|
||||||
|
@@ -14,7 +14,7 @@ import { initialMenusState} from './initial-menus-state';
|
|||||||
import { hasValue } from '../empty.util';
|
import { hasValue } from '../empty.util';
|
||||||
import { MenusState } from './menus-state.model';
|
import { MenusState } from './menus-state.model';
|
||||||
import { MenuState } from './menu-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 { MenuSections } from './menu-sections.model';
|
||||||
import { MenuSection } from './menu-section.model';
|
import { MenuSection } from './menu-section.model';
|
||||||
import { MenuID } from './menu-id.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