mirror of
https://github.com/DSpace/dspace-angular.git
synced 2025-10-07 18:14:17 +00:00
Support withSubs on the menuproviders in appmenu
Adds support to define child providers for a parent menu using a .withSubs option. This parent menu will always be displayed as an expandable menu. When no children are visible, the expandable menus will be hidden.
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
<div class="sidebar-section" [ngClass]="{'expanded': (expanded | async)}"
|
<div *ngIf="hasSubSections$ | async" class="sidebar-section" [ngClass]="{'expanded': (expanded | async)}"
|
||||||
[@bgColor]="{
|
[@bgColor]="{
|
||||||
value: ((expanded | async) ? 'endBackground' : 'startBackground'),
|
value: ((expanded | async) ? 'endBackground' : 'startBackground'),
|
||||||
params: {endColor: (sidebarActiveBg | async)}}">
|
params: {endColor: (sidebarActiveBg | async)}}">
|
||||||
|
@@ -11,6 +11,7 @@ import { map } from 'rxjs/operators';
|
|||||||
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';
|
||||||
import { Router } from '@angular/router';
|
import { Router } from '@angular/router';
|
||||||
|
import { isNotEmpty } from '../../../shared/empty.util';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents a expandable section in the sidebar
|
* Represents a expandable section in the sidebar
|
||||||
@@ -51,6 +52,12 @@ export class ExpandableAdminSidebarSectionComponent extends AdminSidebarSectionC
|
|||||||
*/
|
*/
|
||||||
expanded: Observable<boolean>;
|
expanded: Observable<boolean>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Emits true when the top section has subsections, else emits false
|
||||||
|
*/
|
||||||
|
hasSubSections$: Observable<boolean>;
|
||||||
|
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@Inject('sectionDataProvider') protected section: MenuSection,
|
@Inject('sectionDataProvider') protected section: MenuSection,
|
||||||
protected menuService: MenuService,
|
protected menuService: MenuService,
|
||||||
@@ -66,6 +73,9 @@ export class ExpandableAdminSidebarSectionComponent extends AdminSidebarSectionC
|
|||||||
*/
|
*/
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
super.ngOnInit();
|
super.ngOnInit();
|
||||||
|
this.hasSubSections$ = this.subSections$.pipe(
|
||||||
|
map((subSections) => isNotEmpty(subSections))
|
||||||
|
);
|
||||||
this.sidebarActiveBg = this.variableService.getVariable('--ds-admin-sidebar-active-bg');
|
this.sidebarActiveBg = this.variableService.getVariable('--ds-admin-sidebar-active-bg');
|
||||||
this.sidebarCollapsed = this.menuService.isMenuCollapsed(this.menuID);
|
this.sidebarCollapsed = this.menuService.isMenuCollapsed(this.menuID);
|
||||||
this.sidebarPreviewCollapsed = this.menuService.isMenuPreviewCollapsed(this.menuID);
|
this.sidebarPreviewCollapsed = this.menuService.isMenuPreviewCollapsed(this.menuID);
|
||||||
|
@@ -31,6 +31,7 @@ import { COMMUNITY_MODULE_PATH } from './community-page/community-page-routing-p
|
|||||||
import { COLLECTION_MODULE_PATH } from './collection-page/collection-page-routing-paths';
|
import { COLLECTION_MODULE_PATH } from './collection-page/collection-page-routing-paths';
|
||||||
import { ENTITY_MODULE_PATH, ITEM_MODULE_PATH } from './item-page/item-page-routing-paths';
|
import { ENTITY_MODULE_PATH, ITEM_MODULE_PATH } from './item-page/item-page-routing-paths';
|
||||||
import { HOME_PAGE_PATH } from './app-routing-paths';
|
import { HOME_PAGE_PATH } from './app-routing-paths';
|
||||||
|
import { DsoOptionMenu } from './shared/menu/providers/dso-option-menu.service';
|
||||||
|
|
||||||
export const MENUS = buildMenuStructure({
|
export const MENUS = buildMenuStructure({
|
||||||
[MenuID.PUBLIC]: [
|
[MenuID.PUBLIC]: [
|
||||||
@@ -53,10 +54,13 @@ export const MENUS = buildMenuStructure({
|
|||||||
SystemWideAlertMenuProvider,
|
SystemWideAlertMenuProvider,
|
||||||
],
|
],
|
||||||
[MenuID.DSO_EDIT]: [
|
[MenuID.DSO_EDIT]: [
|
||||||
|
DsoOptionMenu.withSubs([
|
||||||
DSpaceObjectEditMenuProvider.onRoute(COMMUNITY_MODULE_PATH, COLLECTION_MODULE_PATH, ITEM_MODULE_PATH, ENTITY_MODULE_PATH),
|
DSpaceObjectEditMenuProvider.onRoute(COMMUNITY_MODULE_PATH, COLLECTION_MODULE_PATH, ITEM_MODULE_PATH, ENTITY_MODULE_PATH),
|
||||||
VersioningMenuProvider.onRoute(ITEM_MODULE_PATH, ENTITY_MODULE_PATH),
|
VersioningMenuProvider.onRoute(ITEM_MODULE_PATH, ENTITY_MODULE_PATH),
|
||||||
OrcidMenuProvider.onRoute(ITEM_MODULE_PATH, ENTITY_MODULE_PATH),
|
OrcidMenuProvider.onRoute(ITEM_MODULE_PATH, ENTITY_MODULE_PATH),
|
||||||
ClaimMenuProvider.onRoute(ITEM_MODULE_PATH, ENTITY_MODULE_PATH),
|
ClaimMenuProvider.onRoute(ITEM_MODULE_PATH, ENTITY_MODULE_PATH),
|
||||||
|
// SubscribeMenuProvider,
|
||||||
|
]),
|
||||||
SubscribeMenuProvider.onRoute(COMMUNITY_MODULE_PATH, COLLECTION_MODULE_PATH),
|
SubscribeMenuProvider.onRoute(COMMUNITY_MODULE_PATH, COLLECTION_MODULE_PATH),
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
@@ -34,9 +34,6 @@
|
|||||||
</ds-comcol-page-content>
|
</ds-comcol-page-content>
|
||||||
</header>
|
</header>
|
||||||
<ds-dso-edit-menu></ds-dso-edit-menu>
|
<ds-dso-edit-menu></ds-dso-edit-menu>
|
||||||
<div class="pl-2 space-children-mr">
|
|
||||||
<ds-dso-page-subscription-button [dso]="collection"></ds-dso-page-subscription-button>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<section class="comcol-page-browse-section">
|
<section class="comcol-page-browse-section">
|
||||||
<!-- Browse-By Links -->
|
<!-- Browse-By Links -->
|
||||||
|
@@ -21,9 +21,6 @@
|
|||||||
</ds-comcol-page-content>
|
</ds-comcol-page-content>
|
||||||
</header>
|
</header>
|
||||||
<ds-dso-edit-menu></ds-dso-edit-menu>
|
<ds-dso-edit-menu></ds-dso-edit-menu>
|
||||||
<div class="pl-2 space-children-mr">
|
|
||||||
<ds-dso-page-subscription-button [dso]="communityPayload"></ds-dso-page-subscription-button>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<section class="comcol-page-browse-section">
|
<section class="comcol-page-browse-section">
|
||||||
|
|
||||||
|
@@ -191,11 +191,7 @@ export abstract class InitService {
|
|||||||
this.breadcrumbsService.listenForRouteChanges();
|
this.breadcrumbsService.listenForRouteChanges();
|
||||||
this.themeService.listenForRouteChanges();
|
this.themeService.listenForRouteChanges();
|
||||||
// this.menuService.listenForRouteChanges();
|
// this.menuService.listenForRouteChanges();
|
||||||
this.menuProviderService.listenForRouteChanges().subscribe((done) => {
|
this.menuProviderService.listenForRouteChanges();
|
||||||
Object.values(MenuID).forEach((menuID) => {
|
|
||||||
this.menuService.buildRouteMenuSections(menuID);
|
|
||||||
});
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected initPersistentMenus(): void {
|
protected initPersistentMenus(): void {
|
||||||
|
@@ -1,4 +1,5 @@
|
|||||||
<div class="nav-item dropdown expandable-navbar-section text-md-center"
|
<div *ngIf="hasSubSections$ | async">
|
||||||
|
<div class="nav-item dropdown expandable-navbar-section text-md-center"
|
||||||
*ngVar="(active | async) as isActive"
|
*ngVar="(active | async) as isActive"
|
||||||
(keyup.enter)="isActive ? deactivateSection($event) : activateSection($event)"
|
(keyup.enter)="isActive ? deactivateSection($event) : activateSection($event)"
|
||||||
(keyup.space)="isActive ? deactivateSection($event) : activateSection($event)"
|
(keyup.space)="isActive ? deactivateSection($event) : activateSection($event)"
|
||||||
@@ -19,4 +20,5 @@
|
|||||||
*ngComponentOutlet="(sectionMap$ | async).get(subSection.id).component; injector: (sectionMap$ | async).get(subSection.id).injector;"></ng-container>
|
*ngComponentOutlet="(sectionMap$ | async).get(subSection.id).component; injector: (sectionMap$ | async).get(subSection.id).injector;"></ng-container>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
</ul>
|
</ul>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@@ -3,10 +3,12 @@ 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';
|
||||||
import { first } from 'rxjs/operators';
|
import { first, map } from 'rxjs/operators';
|
||||||
import { HostWindowService } from '../../shared/host-window.service';
|
import { HostWindowService } from '../../shared/host-window.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';
|
||||||
|
import { Observable } from 'rxjs';
|
||||||
|
import { isNotEmpty } from '../../shared/empty.util';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents an expandable section in the navbar
|
* Represents an expandable section in the navbar
|
||||||
@@ -24,6 +26,11 @@ export class ExpandableNavbarSectionComponent extends NavbarSectionComponent imp
|
|||||||
*/
|
*/
|
||||||
menuID = MenuID.PUBLIC;
|
menuID = MenuID.PUBLIC;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Emits true when the top section has subsections, else emits false
|
||||||
|
*/
|
||||||
|
hasSubSections$: Observable<boolean>;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@Inject('sectionDataProvider') protected section: MenuSection,
|
@Inject('sectionDataProvider') protected section: MenuSection,
|
||||||
protected menuService: MenuService,
|
protected menuService: MenuService,
|
||||||
@@ -35,6 +42,9 @@ export class ExpandableNavbarSectionComponent extends NavbarSectionComponent imp
|
|||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
super.ngOnInit();
|
super.ngOnInit();
|
||||||
|
this.hasSubSections$ = this.subSections$.pipe(
|
||||||
|
map((subSections) => isNotEmpty(subSections))
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
<div class="dso-button-menu mb-1" ngbDropdown container="body" placement="bottom-right">
|
<div *ngIf="hasSubSections$ | async" class="dso-button-menu mb-1" ngbDropdown container="body" placement="bottom-right">
|
||||||
<div class="d-flex flex-row flex-nowrap"
|
<div class="d-flex flex-row flex-nowrap"
|
||||||
[ngbTooltip]="itemModel.text | translate">
|
[ngbTooltip]="itemModel.text | translate">
|
||||||
<button [attr.aria-label]="itemModel.text | translate" [title]="itemModel.text | translate" class="btn btn-dark btn-sm" ngbDropdownToggle [disabled]="section.model?.disabled">
|
<button [attr.aria-label]="itemModel.text | translate" [title]="itemModel.text | translate" class="btn btn-dark btn-sm" ngbDropdownToggle [disabled]="section.model?.disabled">
|
||||||
|
@@ -7,7 +7,7 @@ import { MenuID } from 'src/app/shared/menu/menu-id.model';
|
|||||||
import { MenuSection } from 'src/app/shared/menu/menu-section.model';
|
import { MenuSection } from 'src/app/shared/menu/menu-section.model';
|
||||||
import { Observable } from 'rxjs';
|
import { Observable } from 'rxjs';
|
||||||
import { map } from 'rxjs/operators';
|
import { map } from 'rxjs/operators';
|
||||||
import { hasValue } from '../../../empty.util';
|
import { hasValue, isNotEmpty } from '../../../empty.util';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents an expandable section in the dso edit menus
|
* Represents an expandable section in the dso edit menus
|
||||||
@@ -21,11 +21,27 @@ import { hasValue } from '../../../empty.util';
|
|||||||
@rendersSectionForMenu(MenuID.DSO_EDIT, true)
|
@rendersSectionForMenu(MenuID.DSO_EDIT, true)
|
||||||
export class DsoEditMenuExpandableSectionComponent extends AbstractMenuSectionComponent {
|
export class DsoEditMenuExpandableSectionComponent extends AbstractMenuSectionComponent {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This section resides in the DSO edit menu
|
||||||
|
*/
|
||||||
menuID: MenuID = MenuID.DSO_EDIT;
|
menuID: MenuID = MenuID.DSO_EDIT;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The MenuItemModel of the top section
|
||||||
|
*/
|
||||||
itemModel;
|
itemModel;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Emits whether one of the subsections contains an icon
|
||||||
|
*/
|
||||||
renderIcons$: Observable<boolean>;
|
renderIcons$: Observable<boolean>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Emits true when the top section has subsections, else emits false
|
||||||
|
*/
|
||||||
|
hasSubSections$: Observable<boolean>;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@Inject('sectionDataProvider') protected section: MenuSection,
|
@Inject('sectionDataProvider') protected section: MenuSection,
|
||||||
protected menuService: MenuService,
|
protected menuService: MenuService,
|
||||||
@@ -45,5 +61,10 @@ export class DsoEditMenuExpandableSectionComponent extends AbstractMenuSectionCo
|
|||||||
return sections.some(section => hasValue(section.icon));
|
return sections.some(section => hasValue(section.icon));
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
this.hasSubSections$ = this.subSections$.pipe(
|
||||||
|
map((subSections) => isNotEmpty(subSections))
|
||||||
|
);
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -28,7 +28,7 @@ export const initialMenusState: MenusState = {
|
|||||||
id: MenuID.DSO_EDIT,
|
id: MenuID.DSO_EDIT,
|
||||||
collapsed: true,
|
collapsed: true,
|
||||||
previewCollapsed: true,
|
previewCollapsed: true,
|
||||||
visible: false,
|
visible: true,
|
||||||
sections: {},
|
sections: {},
|
||||||
sectionToSubsectionIndex: {}
|
sectionToSubsectionIndex: {}
|
||||||
},
|
},
|
||||||
|
@@ -9,12 +9,10 @@
|
|||||||
import { Inject, Injectable, Injector, Optional, } from '@angular/core';
|
import { Inject, Injectable, Injector, Optional, } from '@angular/core';
|
||||||
import { ActivatedRoute, ActivatedRouteSnapshot, ResolveEnd, Router, RouterStateSnapshot, } from '@angular/router';
|
import { ActivatedRoute, ActivatedRouteSnapshot, ResolveEnd, Router, RouterStateSnapshot, } from '@angular/router';
|
||||||
import { combineLatest, map, Observable, } from 'rxjs';
|
import { combineLatest, map, Observable, } from 'rxjs';
|
||||||
import { filter, find, switchMap, take, tap, } from 'rxjs/operators';
|
import { filter, find, switchMap, take, } from 'rxjs/operators';
|
||||||
import { v4 as uuidv4 } from 'uuid';
|
|
||||||
import { hasValue, isNotEmpty } from '../empty.util';
|
import { hasValue, isNotEmpty } from '../empty.util';
|
||||||
import { MenuID } from './menu-id.model';
|
import { MenuID } from './menu-id.model';
|
||||||
import { AbstractMenuProvider } from './menu-provider';
|
import { AbstractMenuProvider, PartialMenuSection } from './menu-provider';
|
||||||
import { MenuSection } from './menu-section.model';
|
|
||||||
import { MenuState } from './menu-state.model';
|
import { MenuState } from './menu-state.model';
|
||||||
import { MenuService } from './menu.service';
|
import { MenuService } from './menu.service';
|
||||||
import { MENU_PROVIDER } from './menu.structure';
|
import { MENU_PROVIDER } from './menu.structure';
|
||||||
@@ -44,8 +42,8 @@ export class MenuProviderService {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
listenForRouteChanges(): Observable<boolean> {
|
listenForRouteChanges() {
|
||||||
return this.router.events.pipe(
|
this.router.events.pipe(
|
||||||
filter(event => event instanceof ResolveEnd),
|
filter(event => event instanceof ResolveEnd),
|
||||||
switchMap((event: ResolveEnd) => {
|
switchMap((event: ResolveEnd) => {
|
||||||
|
|
||||||
@@ -53,7 +51,11 @@ export class MenuProviderService {
|
|||||||
|
|
||||||
return this.resolveRouteMenus(currentRoute, event.state);
|
return this.resolveRouteMenus(currentRoute, event.state);
|
||||||
}),
|
}),
|
||||||
);
|
).subscribe((done) => {
|
||||||
|
Object.values(MenuID).forEach((menuID) => {
|
||||||
|
this.menuService.buildRouteMenuSections(menuID);
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private getCurrentRoute(route: ActivatedRouteSnapshot): ActivatedRouteSnapshot {
|
private getCurrentRoute(route: ActivatedRouteSnapshot): ActivatedRouteSnapshot {
|
||||||
@@ -71,47 +73,30 @@ export class MenuProviderService {
|
|||||||
|
|
||||||
return combineLatest([
|
return combineLatest([
|
||||||
...this.providers
|
...this.providers
|
||||||
|
.map((provider) => {
|
||||||
|
return provider;
|
||||||
|
})
|
||||||
.filter(provider => provider.shouldPersistOnRouteChange)
|
.filter(provider => provider.shouldPersistOnRouteChange)
|
||||||
.map(provider => provider.getSections().pipe(
|
.map(provider => provider.getSections()
|
||||||
tap((sections) => {
|
.pipe(
|
||||||
sections.forEach((section: MenuSection) => {
|
map((sections) => {
|
||||||
this.menuService.addSection(provider.menuID, {
|
return {provider: provider, sections: sections};
|
||||||
...section,
|
}),
|
||||||
id: section.id ?? uuidv4(),
|
)
|
||||||
index: section.index ?? provider.index,
|
)])
|
||||||
shouldPersistOnRouteChange: true,
|
.pipe(
|
||||||
});
|
switchMap((providerWithSections: { provider: AbstractMenuProvider, sections: PartialMenuSection[] }[]) => {
|
||||||
});
|
const waitForMenus = providerWithSections.map((providerWithSection: {
|
||||||
|
provider: AbstractMenuProvider,
|
||||||
|
sections: PartialMenuSection[]
|
||||||
|
}, sectionIndex) => {
|
||||||
|
providerWithSection.sections.forEach((section) => {
|
||||||
|
this.addSection(providerWithSection, section);
|
||||||
|
});
|
||||||
|
return this.waitForMenu$(providerWithSection.provider.menuID);
|
||||||
|
});
|
||||||
|
return [waitForMenus];
|
||||||
}),
|
}),
|
||||||
switchMap(() => this.waitForMenu$(provider.menuID)),
|
|
||||||
)),
|
|
||||||
// ...this.providers
|
|
||||||
// .filter(provider => {
|
|
||||||
// let matchesPath = false;
|
|
||||||
// if (isNotEmpty(provider.activePaths)) {
|
|
||||||
// route.url.forEach((urlSegment) => {
|
|
||||||
// if (provider.activePaths.includes(urlSegment.path)) {
|
|
||||||
// matchesPath = true;
|
|
||||||
// }
|
|
||||||
// })
|
|
||||||
// }
|
|
||||||
// return matchesPath;
|
|
||||||
// })
|
|
||||||
// .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: false,
|
|
||||||
// });
|
|
||||||
// });
|
|
||||||
// }),
|
|
||||||
// switchMap(() => this.waitForMenu$(provider.menuID)),
|
|
||||||
// ))
|
|
||||||
]
|
|
||||||
).pipe(
|
|
||||||
map(done => done.every(Boolean)),
|
map(done => done.every(Boolean)),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -122,78 +107,75 @@ export class MenuProviderService {
|
|||||||
): Observable<boolean> {
|
): Observable<boolean> {
|
||||||
return combineLatest([
|
return combineLatest([
|
||||||
...Object.values(MenuID).map((menuID) => {
|
...Object.values(MenuID).map((menuID) => {
|
||||||
return this.menuService.getNonPersistentMenuSections(menuID)
|
return this.menuService.getNonPersistentMenuSections(menuID).pipe(
|
||||||
.pipe(
|
|
||||||
take(1),
|
take(1),
|
||||||
map((sections) => {
|
map((sections) => {
|
||||||
return {menuId: menuID, sections: sections};
|
return {menuId: menuID, sections: sections};
|
||||||
})
|
}));
|
||||||
);
|
})])
|
||||||
})]).pipe(
|
.pipe(
|
||||||
switchMap((menuSectionsPerMenu) => {
|
switchMap((menuSectionsPerMenu) => {
|
||||||
menuSectionsPerMenu.forEach((menu) => {
|
this.removeNonPersistentSections(menuSectionsPerMenu);
|
||||||
menu.sections.forEach((section) => {
|
|
||||||
this.menuService.removeSection(menu.menuId, section.id);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
return combineLatest([
|
return combineLatest([
|
||||||
// ...this.providers
|
|
||||||
// .filter(provider => isEmpty(provider.activePaths))
|
|
||||||
// .map(provider => provider.getSections(route.snapshot, 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)),
|
|
||||||
// )),
|
|
||||||
...this.providers
|
...this.providers
|
||||||
.filter(provider => {
|
.filter(provider => {
|
||||||
let shouldUpdate = false;
|
let shouldUpdate = false;
|
||||||
if (!provider.shouldPersistOnRouteChange && isNotEmpty(provider.activePaths)) {
|
if (!provider.shouldPersistOnRouteChange && isNotEmpty(provider.activePaths)) {
|
||||||
|
|
||||||
provider.activePaths.forEach((path) => {
|
provider.activePaths.forEach((path) => {
|
||||||
if (state.url.includes(path)) {
|
if (state.url.includes(path)) {
|
||||||
shouldUpdate = true;
|
shouldUpdate = true;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else {
|
} else if (!provider.shouldPersistOnRouteChange) {
|
||||||
if (!provider.shouldPersistOnRouteChange) {
|
|
||||||
|
|
||||||
shouldUpdate = true;
|
shouldUpdate = true;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return shouldUpdate;
|
return shouldUpdate;
|
||||||
})
|
})
|
||||||
.map(provider => provider.getSections(route, state).pipe(
|
.map(provider => provider.getSections(route, state)
|
||||||
tap((sections) => {
|
.pipe(
|
||||||
|
map((sections) => {
|
||||||
sections.forEach((section: MenuSection) => {
|
return {provider: provider, sections: sections};
|
||||||
this.menuService.addSection(provider.menuID, {
|
}),
|
||||||
...section,
|
)
|
||||||
id: section.id ?? uuidv4(),
|
)
|
||||||
index: section.index ?? provider.index,
|
]);
|
||||||
shouldPersistOnRouteChange: false,
|
}),
|
||||||
});
|
switchMap((providerWithSections: { provider: AbstractMenuProvider, sections: PartialMenuSection[] }[]) => {
|
||||||
});
|
const waitForMenus = providerWithSections.map((providerWithSection: {
|
||||||
|
provider: AbstractMenuProvider,
|
||||||
|
sections: PartialMenuSection[]
|
||||||
|
}, sectionIndex) => {
|
||||||
|
providerWithSection.sections.forEach((section) => {
|
||||||
|
this.addSection(providerWithSection, section);
|
||||||
|
});
|
||||||
|
return this.waitForMenu$(providerWithSection.provider.menuID);
|
||||||
|
});
|
||||||
|
return [waitForMenus];
|
||||||
}),
|
}),
|
||||||
switchMap(() => this.waitForMenu$(provider.menuID)),
|
|
||||||
))]
|
|
||||||
).pipe(
|
|
||||||
map(done => done.every(Boolean)),
|
map(done => done.every(Boolean)),
|
||||||
);
|
);
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
)
|
|
||||||
);
|
private addSection(providerWithSection: {
|
||||||
|
provider: AbstractMenuProvider;
|
||||||
|
sections: PartialMenuSection[]
|
||||||
|
}, section: PartialMenuSection) {
|
||||||
|
this.menuService.addSection(providerWithSection.provider.menuID, {
|
||||||
|
...section,
|
||||||
|
id: section.id ?? `${providerWithSection.provider.menuProviderId}`,
|
||||||
|
parentID: section.parentID ?? providerWithSection.provider.parentID,
|
||||||
|
index: section.index ?? providerWithSection.provider.index,
|
||||||
|
shouldPersistOnRouteChange: section.shouldPersistOnRouteChange ?? providerWithSection.provider.shouldPersistOnRouteChange,
|
||||||
|
isExpandable: section.isExpandable ?? providerWithSection.provider.isExpandable,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private removeNonPersistentSections(menuSectionsPerMenu) {
|
||||||
|
menuSectionsPerMenu.forEach((menu) => {
|
||||||
|
menu.sections.forEach((section) => {
|
||||||
|
this.menuService.removeSection(menu.menuId, section.id);
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -18,7 +18,7 @@ import {
|
|||||||
} from 'rxjs';
|
} from 'rxjs';
|
||||||
import { map } from 'rxjs/operators';
|
import { map } from 'rxjs/operators';
|
||||||
import { MenuID } from './menu-id.model';
|
import { MenuID } from './menu-id.model';
|
||||||
import { MenuSection } from './menu-section.model';
|
import { MenuItemModels, MenuSection } from './menu-section.model';
|
||||||
import { APP_INITIALIZER, Provider, Type } from '@angular/core';
|
import { APP_INITIALIZER, Provider, Type } from '@angular/core';
|
||||||
import { APP_CONFIG } from '../../../config/app-config.interface';
|
import { APP_CONFIG } from '../../../config/app-config.interface';
|
||||||
import { TransferState } from '@angular/platform-browser';
|
import { TransferState } from '@angular/platform-browser';
|
||||||
@@ -26,7 +26,19 @@ import { environment } from '../../../environments/environment';
|
|||||||
import { HOME_PAGE_PATH } from '../../app-routing-paths';
|
import { HOME_PAGE_PATH } from '../../app-routing-paths';
|
||||||
import { MENU_PROVIDER } from './menu.structure';
|
import { MENU_PROVIDER } from './menu.structure';
|
||||||
|
|
||||||
export type PartialMenuSection = Omit<MenuSection, 'id' | 'active'>;
|
// export type PartialMenuSection = Omit<MenuSection, 'id' | 'active'>;
|
||||||
|
export interface PartialMenuSection {
|
||||||
|
id?: string;
|
||||||
|
visible: boolean;
|
||||||
|
model: MenuItemModels;
|
||||||
|
parentID?: string;
|
||||||
|
index?: number;
|
||||||
|
active?: boolean;
|
||||||
|
shouldPersistOnRouteChange?: boolean;
|
||||||
|
icon?: string;
|
||||||
|
isExpandable?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
export interface MenuProvider {
|
export interface MenuProvider {
|
||||||
@@ -37,11 +49,25 @@ export interface MenuProvider {
|
|||||||
getSections(route?: ActivatedRouteSnapshot, state?: RouterStateSnapshot): Observable<PartialMenuSection[]>;
|
getSections(route?: ActivatedRouteSnapshot, state?: RouterStateSnapshot): Observable<PartialMenuSection[]>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class MenuProviderTypeWithPaths {
|
||||||
|
providerType: Type<MenuProvider>;
|
||||||
|
paths: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export class MenuProviderTypeWithSubs {
|
||||||
|
providerType: Type<MenuProvider>;
|
||||||
|
childProviderTypes: (Type<MenuProvider> | MenuProviderTypeWithPaths)[];
|
||||||
|
}
|
||||||
|
|
||||||
export abstract class AbstractMenuProvider implements MenuProvider {
|
export abstract class AbstractMenuProvider implements MenuProvider {
|
||||||
shouldPersistOnRouteChange = true;
|
shouldPersistOnRouteChange = true;
|
||||||
menuID?: MenuID;
|
menuID?: MenuID;
|
||||||
|
menuProviderId?: string;
|
||||||
index?: number;
|
index?: number;
|
||||||
activePaths?: string[];
|
activePaths?: string[];
|
||||||
|
parentID?: string;
|
||||||
|
isExpandable = false;
|
||||||
|
|
||||||
|
|
||||||
abstract getSections(route?: ActivatedRouteSnapshot, state?: RouterStateSnapshot): Observable<PartialMenuSection[]>;
|
abstract getSections(route?: ActivatedRouteSnapshot, state?: RouterStateSnapshot): Observable<PartialMenuSection[]>;
|
||||||
|
|
||||||
|
@@ -79,4 +79,6 @@ export interface MenuSection {
|
|||||||
* Note that not all menus may render icons.
|
* Note that not all menus may render icons.
|
||||||
*/
|
*/
|
||||||
icon?: string;
|
icon?: string;
|
||||||
|
|
||||||
|
isExpandable?: boolean;
|
||||||
}
|
}
|
||||||
|
@@ -218,7 +218,7 @@ export class MenuComponent implements OnInit, OnDestroy {
|
|||||||
private getSectionComponent(section: MenuSection): Observable<GenericConstructor<AbstractMenuSectionComponent>> {
|
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 || section.isExpandable, this.themeService.getThemeName());
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
@@ -2,7 +2,7 @@ import { Injectable } from '@angular/core';
|
|||||||
import { createSelector, MemoizedSelector, select, Store } from '@ngrx/store';
|
import { createSelector, MemoizedSelector, select, Store } from '@ngrx/store';
|
||||||
import { AppState, keySelector } from '../../app.reducer';
|
import { AppState, keySelector } from '../../app.reducer';
|
||||||
import { combineLatest as observableCombineLatest, Observable } from 'rxjs';
|
import { combineLatest as observableCombineLatest, Observable } from 'rxjs';
|
||||||
import { filter, map, switchMap, take } from 'rxjs/operators';
|
import { filter, map, switchMap, take, tap } from 'rxjs/operators';
|
||||||
import {
|
import {
|
||||||
ActivateMenuSectionAction,
|
ActivateMenuSectionAction,
|
||||||
AddMenuSectionAction,
|
AddMenuSectionAction,
|
||||||
|
@@ -13,13 +13,40 @@ import {
|
|||||||
import { MenuID } from './menu-id.model';
|
import { MenuID } from './menu-id.model';
|
||||||
import { AbstractMenuProvider } from './menu-provider';
|
import { AbstractMenuProvider } from './menu-provider';
|
||||||
import { MenuProviderService } from './menu-provider.service';
|
import { MenuProviderService } from './menu-provider.service';
|
||||||
|
import { hasValue, isNotEmpty } from '../empty.util';
|
||||||
|
|
||||||
export const MENU_PROVIDER = new InjectionToken<AbstractMenuProvider>('MENU_PROVIDER');
|
export const MENU_PROVIDER = new InjectionToken<AbstractMenuProvider>('MENU_PROVIDER');
|
||||||
|
|
||||||
type MenuStructure = {
|
type MenuStructure = {
|
||||||
[key in MenuID]: (Type<AbstractMenuProvider> | {providerType: Type<AbstractMenuProvider>, paths: string[]})[];
|
[key in MenuID]: (Type<AbstractMenuProvider> | {providerType: Type<AbstractMenuProvider>, paths: string[]} | {providerType: Type<AbstractMenuProvider>, childProviderTypes: any[]})[];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function resolveProvider(providerType: Type<AbstractMenuProvider> , menuID: string, index: number, paths?: string[], parentID?: string, childProviders? : Type<AbstractMenuProvider>[]) {
|
||||||
|
return {
|
||||||
|
provide: MENU_PROVIDER,
|
||||||
|
multi: true,
|
||||||
|
useFactory(provider: AbstractMenuProvider): AbstractMenuProvider {
|
||||||
|
provider.menuID = menuID as MenuID;
|
||||||
|
provider.index = provider.index ?? index;
|
||||||
|
if (hasValue(parentID)) {
|
||||||
|
provider.menuProviderId = `${parentID}_${provider.constructor.name}-${provider.index}`
|
||||||
|
provider.parentID = parentID;
|
||||||
|
} else {
|
||||||
|
provider.menuProviderId = `${provider.constructor.name}-${provider.index}`;
|
||||||
|
}
|
||||||
|
if (isNotEmpty(paths)) {
|
||||||
|
provider.activePaths = paths;
|
||||||
|
provider.shouldPersistOnRouteChange = false;
|
||||||
|
}
|
||||||
|
if (isNotEmpty(childProviders)) {
|
||||||
|
provider.shouldPersistOnRouteChange = false;
|
||||||
|
}
|
||||||
|
return provider;
|
||||||
|
},
|
||||||
|
deps: [providerType],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export function buildMenuStructure(structure: MenuStructure): Provider[] {
|
export function buildMenuStructure(structure: MenuStructure): Provider[] {
|
||||||
const providers: Provider[] = [
|
const providers: Provider[] = [
|
||||||
MenuProviderService,
|
MenuProviderService,
|
||||||
@@ -29,36 +56,39 @@ export function buildMenuStructure(structure: MenuStructure): Provider[] {
|
|||||||
for (const [index, providerType] of providerTypes.entries()) {
|
for (const [index, providerType] of providerTypes.entries()) {
|
||||||
// todo: should complain if not injectable!
|
// todo: should complain if not injectable!
|
||||||
|
|
||||||
if (providerType.hasOwnProperty('providerType')) {
|
if (providerType.hasOwnProperty('providerType') && providerType.hasOwnProperty('paths')) {
|
||||||
const providerPart = (providerType as any).providerType;
|
const providerPart = (providerType as any).providerType;
|
||||||
const paths = (providerType as any).paths;
|
const paths = (providerType as any).paths;
|
||||||
|
providers.push(providerPart);
|
||||||
|
providers.push(resolveProvider(providerPart, menuID, index, paths));
|
||||||
|
} else if (providerType.hasOwnProperty('providerType') && providerType.hasOwnProperty('childProviderTypes')){
|
||||||
|
const providerPart = (providerType as any).providerType;
|
||||||
|
|
||||||
|
const childProviderList = [];
|
||||||
|
const childProviderTypes = (providerType as any).childProviderTypes;
|
||||||
|
childProviderTypes.forEach((childProviderType, childIndex: number) => {
|
||||||
|
|
||||||
|
|
||||||
|
if (childProviderType.hasOwnProperty('providerType') && childProviderType.hasOwnProperty('paths')) {
|
||||||
|
const childProviderTypePart = (childProviderType as any).providerType;
|
||||||
|
const paths = (childProviderType as any).paths;
|
||||||
|
providers.push(childProviderTypePart);
|
||||||
|
providers.push(resolveProvider(childProviderTypePart, menuID, childIndex, paths, `${providerPart.name}-${index}`))
|
||||||
|
childProviderList.push(childProviderTypePart);
|
||||||
|
} else {
|
||||||
|
providers.push(childProviderType)
|
||||||
|
providers.push(resolveProvider(childProviderType, menuID, childIndex, undefined, `${providerPart.name}-${index}`))
|
||||||
|
childProviderList.push(childProviderType);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
providers.push(providerPart);
|
providers.push(providerPart);
|
||||||
providers.push({
|
providers.push(resolveProvider(providerPart, menuID, index, undefined, undefined, childProviderList));
|
||||||
provide: MENU_PROVIDER,
|
|
||||||
multi: true,
|
|
||||||
useFactory(provider: AbstractMenuProvider): AbstractMenuProvider {
|
|
||||||
provider.menuID = menuID as MenuID;
|
|
||||||
provider.index = provider.index ?? index;
|
|
||||||
provider.activePaths = paths;
|
|
||||||
provider.shouldPersistOnRouteChange = false;
|
|
||||||
return provider;
|
|
||||||
},
|
|
||||||
deps: [providerPart],
|
|
||||||
});
|
|
||||||
} else {
|
} else {
|
||||||
providers.push(providerType as Type<AbstractMenuProvider> );
|
providers.push(providerType as Type<AbstractMenuProvider> );
|
||||||
providers.push({
|
providers.push(resolveProvider(providerType as Type<AbstractMenuProvider>, menuID, index));
|
||||||
provide: MENU_PROVIDER,
|
|
||||||
multi: true,
|
|
||||||
useFactory(provider: AbstractMenuProvider): AbstractMenuProvider {
|
|
||||||
provider.menuID = menuID as MenuID;
|
|
||||||
provider.index = provider.index ?? index;
|
|
||||||
return provider;
|
|
||||||
},
|
|
||||||
deps: [providerType],
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@@ -35,10 +35,10 @@ export class SubscribeMenuProvider extends DSpaceObjectPageMenuProvider<Communit
|
|||||||
super(dsoDataService);
|
super(dsoDataService);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected isApplicable(dso: Community | Collection): boolean {
|
// protected isApplicable(dso: Community | Collection): boolean {
|
||||||
// @ts-ignore
|
// // @ts-ignore
|
||||||
return dso.type === COMMUNITY.value || dso.type.value === COLLECTION;
|
// return dso?.type === COMMUNITY.value || dso?.type === COLLECTION.value;
|
||||||
}
|
// }
|
||||||
|
|
||||||
public getSectionsForContext(dso: Community | Collection): Observable<PartialMenuSection[]> {
|
public getSectionsForContext(dso: Community | Collection): Observable<PartialMenuSection[]> {
|
||||||
return combineLatest([
|
return combineLatest([
|
||||||
|
30
src/app/shared/menu/providers/dso-option-menu.service.ts
Normal file
30
src/app/shared/menu/providers/dso-option-menu.service.ts
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
/**
|
||||||
|
* 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 { AbstractExpandableParentMenuProvider } from './expandable-parent-menu-provider';
|
||||||
|
import { PartialMenuSection } from '../menu-provider';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class DsoOptionMenu extends AbstractExpandableParentMenuProvider {
|
||||||
|
|
||||||
|
public getSections(): Observable<PartialMenuSection[]> {
|
||||||
|
return of([
|
||||||
|
{
|
||||||
|
visible: true,
|
||||||
|
model: {
|
||||||
|
type: MenuItemType.TEXT,
|
||||||
|
text: `menu.section.browse_global_communities_and_collections`,
|
||||||
|
},
|
||||||
|
icon: 'diagram-project'
|
||||||
|
},
|
||||||
|
] as PartialMenuSection[]);
|
||||||
|
}
|
||||||
|
}
|
@@ -17,6 +17,7 @@ import {
|
|||||||
AbstractMenuProvider,
|
AbstractMenuProvider,
|
||||||
PartialMenuSection,
|
PartialMenuSection,
|
||||||
} from '../menu-provider';
|
} from '../menu-provider';
|
||||||
|
import { Type } from '@angular/core';
|
||||||
|
|
||||||
export type MenuTopSection = Omit<PartialMenuSection, 'visible'>;
|
export type MenuTopSection = Omit<PartialMenuSection, 'visible'>;
|
||||||
export type MenuSubSection = Omit<PartialMenuSection, 'parentID'>;
|
export type MenuSubSection = Omit<PartialMenuSection, 'parentID'>;
|
||||||
@@ -34,7 +35,6 @@ export abstract class AbstractExpandableMenuProvider extends AbstractMenuProvide
|
|||||||
|
|
||||||
getSections(): Observable<PartialMenuSection[]> {
|
getSections(): Observable<PartialMenuSection[]> {
|
||||||
const full = this.includeSubSections();
|
const full = this.includeSubSections();
|
||||||
const parentID = uuidv4();
|
|
||||||
|
|
||||||
return combineLatest([
|
return combineLatest([
|
||||||
this.getTopSection(),
|
this.getTopSection(),
|
||||||
@@ -43,10 +43,11 @@ export abstract class AbstractExpandableMenuProvider extends AbstractMenuProvide
|
|||||||
map((
|
map((
|
||||||
[partialTopSection, partialSubSections]: [MenuTopSection, MenuSubSection[]]
|
[partialTopSection, partialSubSections]: [MenuTopSection, MenuSubSection[]]
|
||||||
) => {
|
) => {
|
||||||
const subSections = partialSubSections.map(partialSub => {
|
const subSections = partialSubSections.map((partialSub, index) => {
|
||||||
return {
|
return {
|
||||||
...partialSub,
|
...partialSub,
|
||||||
parentID: parentID,
|
id: partialSub.id ?? `${this.menuProviderId}_Sub-${index}`,
|
||||||
|
parentID: this.menuProviderId,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -54,7 +55,7 @@ export abstract class AbstractExpandableMenuProvider extends AbstractMenuProvide
|
|||||||
...subSections,
|
...subSections,
|
||||||
{
|
{
|
||||||
...partialTopSection,
|
...partialTopSection,
|
||||||
id: parentID,
|
id: this.menuProviderId,
|
||||||
visible: full ? subSections.some(sub => sub.visible) : this.showWithoutSubsections,
|
visible: full ? subSections.some(sub => sub.visible) : this.showWithoutSubsections,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
@@ -0,0 +1,37 @@
|
|||||||
|
/**
|
||||||
|
* The contents of this file are subject to the license and copyright
|
||||||
|
* detailed in the LICENSE and NOTICE files at the root of the source
|
||||||
|
* tree and available online at
|
||||||
|
*
|
||||||
|
* http://www.dspace.org/license/
|
||||||
|
*/
|
||||||
|
import { combineLatest, Observable, of as observableOf, } from 'rxjs';
|
||||||
|
import { map } from 'rxjs/operators';
|
||||||
|
import {
|
||||||
|
AbstractMenuProvider,
|
||||||
|
MenuProvider,
|
||||||
|
MenuProviderTypeWithPaths,
|
||||||
|
MenuProviderTypeWithSubs,
|
||||||
|
} from '../menu-provider';
|
||||||
|
import { Inject, Injector, Optional, Type } from '@angular/core';
|
||||||
|
import { AbstractExpandableMenuProvider, MenuSubSection } from './expandable-menu-provider';
|
||||||
|
import { ActivatedRoute, ActivatedRouteSnapshot, Router, RouterStateSnapshot } from '@angular/router';
|
||||||
|
import { isEmpty } from '../../empty.util';
|
||||||
|
import { MENU_PROVIDER } from '../menu.structure';
|
||||||
|
import { MenuService } from '../menu.service';
|
||||||
|
|
||||||
|
|
||||||
|
export abstract class AbstractExpandableParentMenuProvider extends AbstractMenuProvider {
|
||||||
|
isExpandable = true;
|
||||||
|
|
||||||
|
public static withSubs(childProviders: (Type<MenuProvider>| MenuProviderTypeWithPaths)[]) {
|
||||||
|
if (!AbstractMenuProvider.isPrototypeOf(this)) {
|
||||||
|
throw new Error(
|
||||||
|
'onRoute should only be called from concrete subclasses of AbstractMenuProvider'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const providerType = this as unknown as Type<AbstractMenuProvider>;
|
||||||
|
return {providerType: providerType, childProviderTypes: childProviders};
|
||||||
|
}
|
||||||
|
}
|
Reference in New Issue
Block a user