From ff0750d0532e420da1891998d403b5efd2061dbd Mon Sep 17 00:00:00 2001 From: Kristof De Langhe Date: Tue, 19 May 2020 14:37:17 +0200 Subject: [PATCH] 68067: Add menu sections through route data --- .../admin-sidebar/admin-sidebar.component.ts | 8 ++- src/app/navbar/navbar.component.ts | 12 ++-- src/app/shared/menu/menu.actions.ts | 14 +++++ src/app/shared/menu/menu.component.ts | 55 ++++++++++++++++++- src/app/shared/menu/menu.reducer.ts | 6 +- src/app/shared/menu/menu.service.ts | 16 ++++-- 6 files changed, 93 insertions(+), 18 deletions(-) diff --git a/src/app/+admin/admin-sidebar/admin-sidebar.component.ts b/src/app/+admin/admin-sidebar/admin-sidebar.component.ts index 5d885b918b..d3633c86b2 100644 --- a/src/app/+admin/admin-sidebar/admin-sidebar.component.ts +++ b/src/app/+admin/admin-sidebar/admin-sidebar.component.ts @@ -18,6 +18,7 @@ import { TextMenuItemModel } from '../../shared/menu/menu-item/models/text.model import { MenuComponent } from '../../shared/menu/menu.component'; import { MenuService } from '../../shared/menu/menu.service'; import { CSSVariableService } from '../../shared/sass-helper/sass-helper.service'; +import { ActivatedRoute, Router } from '@angular/router'; /** * Component representing the admin sidebar @@ -59,18 +60,19 @@ export class AdminSidebarComponent extends MenuComponent implements OnInit { constructor(protected menuService: MenuService, protected injector: Injector, + protected route: ActivatedRoute, + protected router: Router, private variableService: CSSVariableService, private authService: AuthService, private modalService: NgbModal ) { - super(menuService, injector); + super(menuService, injector, route, router); } /** * Set and calculate all initial values of the instance variables */ ngOnInit(): void { - this.createMenu(); super.ngOnInit(); this.sidebarWidth = this.variableService.getVariable('sidebarItemsWidth'); this.authService.isAuthenticated() @@ -93,7 +95,7 @@ export class AdminSidebarComponent extends MenuComponent implements OnInit { /** * Initialize all menu sections and items for this menu */ - private createMenu() { + createMenu() { const menuList = [ /* News */ { diff --git a/src/app/navbar/navbar.component.ts b/src/app/navbar/navbar.component.ts index 6055aac263..99e44ff489 100644 --- a/src/app/navbar/navbar.component.ts +++ b/src/app/navbar/navbar.component.ts @@ -7,6 +7,7 @@ import { TextMenuItemModel } from '../shared/menu/menu-item/models/text.model'; import { LinkMenuItemModel } from '../shared/menu/menu-item/models/link.model'; import { HostWindowService } from '../shared/host-window.service'; import { environment } from '../../environments/environment'; +import { ActivatedRoute, Router } from '@angular/router'; /** * Component representing the public navbar @@ -17,7 +18,7 @@ import { environment } from '../../environments/environment'; templateUrl: './navbar.component.html', animations: [slideMobileNav] }) -export class NavbarComponent extends MenuComponent implements OnInit { +export class NavbarComponent extends MenuComponent { /** * The menu ID of the Navbar is PUBLIC * @type {MenuID.PUBLIC} @@ -26,14 +27,11 @@ export class NavbarComponent extends MenuComponent implements OnInit { constructor(protected menuService: MenuService, protected injector: Injector, + protected route: ActivatedRoute, + protected router: Router, public windowService: HostWindowService ) { - super(menuService, injector); - } - - ngOnInit(): void { - this.createMenu(); - super.ngOnInit(); + super(menuService, injector, route, router); } /** diff --git a/src/app/shared/menu/menu.actions.ts b/src/app/shared/menu/menu.actions.ts index 00275441d6..5b9166641e 100644 --- a/src/app/shared/menu/menu.actions.ts +++ b/src/app/shared/menu/menu.actions.ts @@ -21,6 +21,7 @@ export const MenuActionTypes = { EXPAND_MENU_PREVIEW: type('dspace/menu/EXPAND_MENU_PREVIEW'), ADD_SECTION: type('dspace/menu-section/ADD_SECTION'), REMOVE_SECTION: type('dspace/menu-section/REMOVE_SECTION'), + RESET_SECTIONS: type('dspace/menu-section/RESET_SECTIONS'), SHOW_SECTION: type('dspace/menu-section/SHOW_SECTION'), HIDE_SECTION: type('dspace/menu-section/HIDE_SECTION'), ACTIVATE_SECTION: type('dspace/menu-section/ACTIVATE_SECTION'), @@ -115,6 +116,18 @@ export class ExpandMenuPreviewAction implements Action { } } +/** + * Action used to remove all sections from a certain menu + */ +export class ResetMenuSectionsAction implements Action { + type = MenuActionTypes.RESET_SECTIONS; + menuID: MenuID; + + constructor(menuID: MenuID) { + this.menuID = menuID; + } +} + // MENU SECTION ACTIONS /** * Action used to perform state changes for a section of a certain menu @@ -225,4 +238,5 @@ export type MenuAction = | ToggleActiveMenuSectionAction | CollapseMenuPreviewAction | ExpandMenuPreviewAction + | ResetMenuSectionsAction /* tslint:enable:max-classes-per-file */ diff --git a/src/app/shared/menu/menu.component.ts b/src/app/shared/menu/menu.component.ts index 29a3e0a30a..4e29ea0ee0 100644 --- a/src/app/shared/menu/menu.component.ts +++ b/src/app/shared/menu/menu.component.ts @@ -3,11 +3,13 @@ import { Observable } from 'rxjs/internal/Observable'; import { MenuService } from '../../shared/menu/menu.service'; import { MenuID } from '../../shared/menu/initial-menus-state'; import { MenuSection } from '../../shared/menu/menu.reducer'; -import { distinctUntilChanged, first, map, tap } from 'rxjs/operators'; +import { distinctUntilChanged, filter, first, map, switchMap, tap } from 'rxjs/operators'; import { GenericConstructor } from '../../core/shared/generic-constructor'; -import { hasValue } from '../empty.util'; +import { hasNoValue, hasValue } from '../empty.util'; import { MenuSectionComponent } from './menu-section/menu-section.component'; import { getComponentForMenu } from './menu-section.decorator'; +import { ActivatedRoute, NavigationEnd, Router } from '@angular/router'; +import { of as observableOf } from 'rxjs/internal/observable/of'; /** * A basic implementation of a MenuComponent @@ -62,13 +64,17 @@ export class MenuComponent implements OnInit { */ private previewTimer; - constructor(protected menuService: MenuService, protected injector: Injector) { + constructor(protected menuService: MenuService, + protected injector: Injector, + protected route: ActivatedRoute, + protected router: Router) { } /** * Sets all instance variables to their initial values */ ngOnInit(): void { + this.initSections(); this.menuCollapsed = this.menuService.isMenuCollapsed(this.menuID); this.menuPreviewCollapsed = this.menuService.isMenuPreviewCollapsed(this.menuID); this.menuVisible = this.menuService.isMenuVisible(this.menuID); @@ -81,6 +87,49 @@ export class MenuComponent implements OnInit { }); } + /** + * Initialize all menu sections and add them to the menu's store + */ + initSections() { + this.router.events.pipe( + filter((e): e is NavigationEnd => e instanceof NavigationEnd), + tap(() => this.menuService.resetSections(this.menuID)), + map(() => this.resolveMenuSections(this.route.root)) + ).subscribe((sections: MenuSection[]) => { + this.createMenu(); + sections.forEach((section: MenuSection) => { + this.menuService.addSection(this.menuID, section); + }); + }); + } + + /** + * Initialize all static menu sections and items for this menu + * These sections will be available on any route. Route specific sections should be defined in the route's data instead. + */ + createMenu() { + // Override this method to create and add a standard set of menu sections that should be available for the current menu type on all routes + } + + /** + * Resolve menu sections defined in the current route data (including parent routes) + * @param route The route to resolve data for + */ + resolveMenuSections(route: ActivatedRoute): MenuSection[] { + const data = route.snapshot.data; + const last: boolean = hasNoValue(route.firstChild); + + if (hasValue(data) && hasValue(data.menu)) { + if (!last) { + return [...data.menu, ...this.resolveMenuSections(route.firstChild)] + } else { + return [...data.menu]; + } + } + + return !last ? this.resolveMenuSections(route.firstChild) : []; + } + /** * Collapse this menu when it's currently expanded, expand it when its currently collapsed * @param {Event} event The user event that triggered this method diff --git a/src/app/shared/menu/menu.reducer.ts b/src/app/shared/menu/menu.reducer.ts index e5a30770d8..85d6847e69 100644 --- a/src/app/shared/menu/menu.reducer.ts +++ b/src/app/shared/menu/menu.reducer.ts @@ -6,7 +6,7 @@ import { MenuAction, MenuActionTypes, MenuSectionAction, - RemoveMenuSectionAction, + RemoveMenuSectionAction, ResetMenuSectionsAction, ShowMenuSectionAction, ToggleActiveMenuSectionAction } from './menu.actions'; @@ -118,6 +118,10 @@ export function menusReducer(state: MenusState = initialMenusState, action: Menu case MenuActionTypes.SHOW_SECTION: { return showSection(state, action as ShowMenuSectionAction); } + case MenuActionTypes.RESET_SECTIONS: { + const newMenuState = Object.assign({}, menuState, { sections: {} }); + return Object.assign({}, state, { [action.menuID]: newMenuState }); + } default: { return state; diff --git a/src/app/shared/menu/menu.service.ts b/src/app/shared/menu/menu.service.ts index 95611daa21..9ff4c2f788 100644 --- a/src/app/shared/menu/menu.service.ts +++ b/src/app/shared/menu/menu.service.ts @@ -14,12 +14,12 @@ import { ExpandMenuAction, ExpandMenuPreviewAction, HideMenuAction, - RemoveMenuSectionAction, + RemoveMenuSectionAction, ResetMenuSectionsAction, ShowMenuAction, ToggleActiveMenuSectionAction, ToggleMenuAction, } from './menu.actions'; -import { hasNoValue, hasValue, isNotEmpty } from '../empty.util'; +import { hasNoValue, hasValue, hasValueOperator, isNotEmpty } from '../empty.util'; export function menuKeySelector(key: string, selector): MemoizedSelector { return createSelector(selector, (state) => { @@ -98,7 +98,7 @@ export class MenuService { switchMap((ids: string[]) => observableCombineLatest(ids.map((id: string) => this.getMenuSection(menuID, id))) ), - map((sections: MenuSection[]) => sections.filter((section: MenuSection) => !mustBeVisible || section.visible)) + map((sections: MenuSection[]) => sections.filter((section: MenuSection) => hasValue(section) && (!mustBeVisible || section.visible))) ); } @@ -147,6 +147,14 @@ export class MenuService { this.store.dispatch(new RemoveMenuSectionAction(menuID, sectionID)); } + /** + * Remove all sections within a menu from the store + * @param {MenuID} menuID The menu from which the sections are to be removed + */ + resetSections(menuID: MenuID) { + this.store.dispatch(new ResetMenuSectionsAction(menuID)); + } + /** * Check if a given menu is collapsed * @param {MenuID} menuID The ID of the menu that is to be checked @@ -270,7 +278,7 @@ export class MenuService { * @returns {Observable} Emits true when the given section is currently active, false when the given section is currently inactive */ isSectionActive(menuID: MenuID, id: string): Observable { - return this.getMenuSection(menuID, id).pipe(map((section) => section.active)); + return this.getMenuSection(menuID, id).pipe(hasValueOperator(), map((section) => section.active)); } /**