diff --git a/src/app/+admin/admin-sidebar/admin-sidebar.component.ts b/src/app/+admin/admin-sidebar/admin-sidebar.component.ts index d3633c86b2..f11313fb6d 100644 --- a/src/app/+admin/admin-sidebar/admin-sidebar.component.ts +++ b/src/app/+admin/admin-sidebar/admin-sidebar.component.ts @@ -18,7 +18,6 @@ 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 @@ -60,19 +59,18 @@ 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, route, router); + super(menuService, injector); } /** * 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() @@ -455,7 +453,9 @@ export class AdminSidebarComponent extends MenuComponent implements OnInit { index: 10 }, ]; - menuList.forEach((menuSection) => this.menuService.addSection(this.menuID, menuSection)); + menuList.forEach((menuSection) => this.menuService.addSection(this.menuID, Object.assign(menuSection, { + shouldPersistOnRouteChange: true + }))); } diff --git a/src/app/core/core.effects.ts b/src/app/core/core.effects.ts index 9f4242cc9a..cd14a527e8 100644 --- a/src/app/core/core.effects.ts +++ b/src/app/core/core.effects.ts @@ -7,6 +7,7 @@ import { ServerSyncBufferEffects } from './cache/server-sync-buffer.effects'; import { ObjectUpdatesEffects } from './data/object-updates/object-updates.effects'; import { RouteEffects } from './services/route.effects'; import { RouterEffects } from './router/router.effects'; +import { MenuEffects } from '../shared/menu/menu.effects'; export const coreEffects = [ RequestEffects, @@ -17,5 +18,6 @@ export const coreEffects = [ ServerSyncBufferEffects, ObjectUpdatesEffects, RouteEffects, - RouterEffects + RouterEffects, + MenuEffects ]; diff --git a/src/app/navbar/navbar.component.ts b/src/app/navbar/navbar.component.ts index 99e44ff489..ae9000352a 100644 --- a/src/app/navbar/navbar.component.ts +++ b/src/app/navbar/navbar.component.ts @@ -7,7 +7,6 @@ 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 @@ -27,11 +26,14 @@ export class NavbarComponent extends MenuComponent { constructor(protected menuService: MenuService, protected injector: Injector, - protected route: ActivatedRoute, - protected router: Router, public windowService: HostWindowService ) { - super(menuService, injector, route, router); + super(menuService, injector); + } + + ngOnInit(): void { + this.createMenu(); + super.ngOnInit(); } /** @@ -91,7 +93,9 @@ export class NavbarComponent extends MenuComponent { } as LinkMenuItemModel }); }); - menuList.forEach((menuSection) => this.menuService.addSection(this.menuID, menuSection)); + menuList.forEach((menuSection) => this.menuService.addSection(this.menuID, Object.assign(menuSection, { + shouldPersistOnRouteChange: true + }))); } diff --git a/src/app/shared/menu/menu.actions.ts b/src/app/shared/menu/menu.actions.ts index 5b9166641e..00275441d6 100644 --- a/src/app/shared/menu/menu.actions.ts +++ b/src/app/shared/menu/menu.actions.ts @@ -21,7 +21,6 @@ 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'), @@ -116,18 +115,6 @@ 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 @@ -238,5 +225,4 @@ 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 074d0f1e95..329fabf703 100644 --- a/src/app/shared/menu/menu.component.ts +++ b/src/app/shared/menu/menu.component.ts @@ -8,7 +8,6 @@ import { GenericConstructor } from '../../core/shared/generic-constructor'; 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 { Subscription } from 'rxjs/internal/Subscription'; /** @@ -70,17 +69,13 @@ export class MenuComponent implements OnInit, OnDestroy { */ subs: Subscription[] = []; - constructor(protected menuService: MenuService, - protected injector: Injector, - protected route: ActivatedRoute, - protected router: Router) { + constructor(protected menuService: MenuService, protected injector: Injector) { } /** * 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); @@ -93,49 +88,6 @@ export class MenuComponent implements OnInit, OnDestroy { })); } - /** - * Initialize all menu sections and add them to the menu's store - */ - initSections() { - this.subs.push(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) && hasValue(data.menu[this.menuID])) { - if (!last) { - return [...data.menu[this.menuID], ...this.resolveMenuSections(route.firstChild)] - } else { - return [...data.menu[this.menuID]]; - } - } - - 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.effects.ts b/src/app/shared/menu/menu.effects.ts new file mode 100644 index 0000000000..f0a6c3f958 --- /dev/null +++ b/src/app/shared/menu/menu.effects.ts @@ -0,0 +1,72 @@ +import { ActivatedRoute } from '@angular/router'; +import { MenuSection } from './menu.reducer'; +import { hasNoValue, hasValue } from '../empty.util'; +import { Actions, Effect, ofType } from '@ngrx/effects'; +import { MenuService } from './menu.service'; +import { Observable } from 'rxjs/internal/Observable'; +import { Action } from '@ngrx/store'; +import { ROUTER_NAVIGATED } from '@ngrx/router-store'; +import { Injectable } from '@angular/core'; +import { map, take, tap } from 'rxjs/operators'; +import { MenuID } from './initial-menus-state'; + +@Injectable() +export class MenuEffects { + + constructor(private actions$: Actions, + private menuService: MenuService, + private route: ActivatedRoute) { + } + + @Effect({ dispatch: false }) + public buildRouteMenuSections$: Observable = this.actions$ + .pipe( + ofType(ROUTER_NAVIGATED), + tap(() => { + Object.values(MenuID).forEach((menuID) => { + this.buildRouteMenuSections(menuID); + }); + }) + ); + + buildRouteMenuSections(menuID: MenuID) { + this.menuService.getNonPersistentMenuSections(menuID).pipe( + map((sections) => sections.map((section) => section.id)), + take(1) + ).subscribe((shouldNotPersistIDs: string[]) => { + const resolvedSections = this.resolveRouteMenuSections(this.route.root, menuID); + resolvedSections.forEach((section) => { + const index = shouldNotPersistIDs.indexOf(section.id); + if (index > -1) { + shouldNotPersistIDs.splice(index, 1); + } else { + this.menuService.addSection(menuID, section); + } + }); + shouldNotPersistIDs.forEach((id) => { + this.menuService.removeSection(menuID, id); + }); + }); + } + + /** + * Resolve menu sections defined in the current route data (including parent routes) + * @param route The route to resolve data for + * @param menuID The menu to resolve data for + */ + resolveRouteMenuSections(route: ActivatedRoute, menuID: MenuID): MenuSection[] { + const data = route.snapshot.data; + const last: boolean = hasNoValue(route.firstChild); + + if (hasValue(data) && hasValue(data.menu) && hasValue(data.menu[menuID])) { + if (!last) { + return [...data.menu[menuID], ...this.resolveRouteMenuSections(route.firstChild, menuID)] + } else { + return [...data.menu[menuID]]; + } + } + + return !last ? this.resolveRouteMenuSections(route.firstChild, menuID) : []; + } + +} diff --git a/src/app/shared/menu/menu.reducer.ts b/src/app/shared/menu/menu.reducer.ts index 85d6847e69..0ef328aa15 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, ResetMenuSectionsAction, + RemoveMenuSectionAction, ShowMenuSectionAction, ToggleActiveMenuSectionAction } from './menu.actions'; @@ -58,6 +58,7 @@ export class MenuSection { model: MenuItemModel; index?: number; icon?: string; + shouldPersistOnRouteChange? = false; } /** @@ -118,10 +119,6 @@ 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 9ff4c2f788..fd3ba1ed5c 100644 --- a/src/app/shared/menu/menu.service.ts +++ b/src/app/shared/menu/menu.service.ts @@ -14,7 +14,7 @@ import { ExpandMenuAction, ExpandMenuPreviewAction, HideMenuAction, - RemoveMenuSectionAction, ResetMenuSectionsAction, + RemoveMenuSectionAction, ShowMenuAction, ToggleActiveMenuSectionAction, ToggleMenuAction, @@ -129,6 +129,16 @@ export class MenuService { ); } + /** + * Retrieve menu sections that shouldn't persist on route change + * @param menuID The ID of the menu the sections reside in + */ + getNonPersistentMenuSections(menuID: MenuID): Observable { + return this.getMenu(menuID).pipe( + map((state: MenuState) => Object.values(state.sections).filter((section: MenuSection) => !section.shouldPersistOnRouteChange)) + ); + } + /** * Add a new section to the store * @param {MenuID} menuID The menu to which the new section is to be added @@ -147,14 +157,6 @@ 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