68067: Add menu sections through route data

This commit is contained in:
Kristof De Langhe
2020-05-19 14:37:17 +02:00
parent 172d0d986b
commit ff0750d053
6 changed files with 93 additions and 18 deletions

View File

@@ -18,6 +18,7 @@ import { TextMenuItemModel } from '../../shared/menu/menu-item/models/text.model
import { MenuComponent } from '../../shared/menu/menu.component'; import { MenuComponent } from '../../shared/menu/menu.component';
import { MenuService } from '../../shared/menu/menu.service'; import { MenuService } from '../../shared/menu/menu.service';
import { CSSVariableService } from '../../shared/sass-helper/sass-helper.service'; import { CSSVariableService } from '../../shared/sass-helper/sass-helper.service';
import { ActivatedRoute, Router } from '@angular/router';
/** /**
* Component representing the admin sidebar * Component representing the admin sidebar
@@ -59,18 +60,19 @@ export class AdminSidebarComponent extends MenuComponent implements OnInit {
constructor(protected menuService: MenuService, constructor(protected menuService: MenuService,
protected injector: Injector, protected injector: Injector,
protected route: ActivatedRoute,
protected router: Router,
private variableService: CSSVariableService, private variableService: CSSVariableService,
private authService: AuthService, private authService: AuthService,
private modalService: NgbModal private modalService: NgbModal
) { ) {
super(menuService, injector); super(menuService, injector, route, router);
} }
/** /**
* Set and calculate all initial values of the instance variables * Set and calculate all initial values of the instance variables
*/ */
ngOnInit(): void { ngOnInit(): void {
this.createMenu();
super.ngOnInit(); super.ngOnInit();
this.sidebarWidth = this.variableService.getVariable('sidebarItemsWidth'); this.sidebarWidth = this.variableService.getVariable('sidebarItemsWidth');
this.authService.isAuthenticated() this.authService.isAuthenticated()
@@ -93,7 +95,7 @@ export class AdminSidebarComponent extends MenuComponent implements OnInit {
/** /**
* Initialize all menu sections and items for this menu * Initialize all menu sections and items for this menu
*/ */
private createMenu() { createMenu() {
const menuList = [ const menuList = [
/* News */ /* News */
{ {

View File

@@ -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 { LinkMenuItemModel } from '../shared/menu/menu-item/models/link.model';
import { HostWindowService } from '../shared/host-window.service'; import { HostWindowService } from '../shared/host-window.service';
import { environment } from '../../environments/environment'; import { environment } from '../../environments/environment';
import { ActivatedRoute, Router } from '@angular/router';
/** /**
* Component representing the public navbar * Component representing the public navbar
@@ -17,7 +18,7 @@ import { environment } from '../../environments/environment';
templateUrl: './navbar.component.html', templateUrl: './navbar.component.html',
animations: [slideMobileNav] animations: [slideMobileNav]
}) })
export class NavbarComponent extends MenuComponent implements OnInit { export class NavbarComponent extends MenuComponent {
/** /**
* The menu ID of the Navbar is PUBLIC * The menu ID of the Navbar is PUBLIC
* @type {MenuID.PUBLIC} * @type {MenuID.PUBLIC}
@@ -26,14 +27,11 @@ export class NavbarComponent extends MenuComponent implements OnInit {
constructor(protected menuService: MenuService, constructor(protected menuService: MenuService,
protected injector: Injector, protected injector: Injector,
protected route: ActivatedRoute,
protected router: Router,
public windowService: HostWindowService public windowService: HostWindowService
) { ) {
super(menuService, injector); super(menuService, injector, route, router);
}
ngOnInit(): void {
this.createMenu();
super.ngOnInit();
} }
/** /**

View File

@@ -21,6 +21,7 @@ export const MenuActionTypes = {
EXPAND_MENU_PREVIEW: type('dspace/menu/EXPAND_MENU_PREVIEW'), EXPAND_MENU_PREVIEW: type('dspace/menu/EXPAND_MENU_PREVIEW'),
ADD_SECTION: type('dspace/menu-section/ADD_SECTION'), ADD_SECTION: type('dspace/menu-section/ADD_SECTION'),
REMOVE_SECTION: type('dspace/menu-section/REMOVE_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'), SHOW_SECTION: type('dspace/menu-section/SHOW_SECTION'),
HIDE_SECTION: type('dspace/menu-section/HIDE_SECTION'), HIDE_SECTION: type('dspace/menu-section/HIDE_SECTION'),
ACTIVATE_SECTION: type('dspace/menu-section/ACTIVATE_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 // MENU SECTION ACTIONS
/** /**
* Action used to perform state changes for a section of a certain menu * Action used to perform state changes for a section of a certain menu
@@ -225,4 +238,5 @@ export type MenuAction =
| ToggleActiveMenuSectionAction | ToggleActiveMenuSectionAction
| CollapseMenuPreviewAction | CollapseMenuPreviewAction
| ExpandMenuPreviewAction | ExpandMenuPreviewAction
| ResetMenuSectionsAction
/* tslint:enable:max-classes-per-file */ /* tslint:enable:max-classes-per-file */

View File

@@ -3,11 +3,13 @@ import { Observable } from 'rxjs/internal/Observable';
import { MenuService } from '../../shared/menu/menu.service'; import { MenuService } from '../../shared/menu/menu.service';
import { MenuID } from '../../shared/menu/initial-menus-state'; import { MenuID } from '../../shared/menu/initial-menus-state';
import { MenuSection } from '../../shared/menu/menu.reducer'; 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 { 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 { MenuSectionComponent } from './menu-section/menu-section.component';
import { getComponentForMenu } from './menu-section.decorator'; 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 * A basic implementation of a MenuComponent
@@ -62,13 +64,17 @@ export class MenuComponent implements OnInit {
*/ */
private previewTimer; 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 * Sets all instance variables to their initial values
*/ */
ngOnInit(): void { ngOnInit(): void {
this.initSections();
this.menuCollapsed = this.menuService.isMenuCollapsed(this.menuID); this.menuCollapsed = this.menuService.isMenuCollapsed(this.menuID);
this.menuPreviewCollapsed = this.menuService.isMenuPreviewCollapsed(this.menuID); this.menuPreviewCollapsed = this.menuService.isMenuPreviewCollapsed(this.menuID);
this.menuVisible = this.menuService.isMenuVisible(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 * Collapse this menu when it's currently expanded, expand it when its currently collapsed
* @param {Event} event The user event that triggered this method * @param {Event} event The user event that triggered this method

View File

@@ -6,7 +6,7 @@ import {
MenuAction, MenuAction,
MenuActionTypes, MenuActionTypes,
MenuSectionAction, MenuSectionAction,
RemoveMenuSectionAction, RemoveMenuSectionAction, ResetMenuSectionsAction,
ShowMenuSectionAction, ShowMenuSectionAction,
ToggleActiveMenuSectionAction ToggleActiveMenuSectionAction
} from './menu.actions'; } from './menu.actions';
@@ -118,6 +118,10 @@ export function menusReducer(state: MenusState = initialMenusState, action: Menu
case MenuActionTypes.SHOW_SECTION: { case MenuActionTypes.SHOW_SECTION: {
return showSection(state, action as ShowMenuSectionAction); return showSection(state, action as ShowMenuSectionAction);
} }
case MenuActionTypes.RESET_SECTIONS: {
const newMenuState = Object.assign({}, menuState, { sections: {} });
return Object.assign({}, state, { [action.menuID]: newMenuState });
}
default: { default: {
return state; return state;

View File

@@ -14,12 +14,12 @@ import {
ExpandMenuAction, ExpandMenuAction,
ExpandMenuPreviewAction, ExpandMenuPreviewAction,
HideMenuAction, HideMenuAction,
RemoveMenuSectionAction, RemoveMenuSectionAction, ResetMenuSectionsAction,
ShowMenuAction, ShowMenuAction,
ToggleActiveMenuSectionAction, ToggleActiveMenuSectionAction,
ToggleMenuAction, ToggleMenuAction,
} from './menu.actions'; } from './menu.actions';
import { hasNoValue, hasValue, isNotEmpty } from '../empty.util'; import { hasNoValue, hasValue, hasValueOperator, isNotEmpty } from '../empty.util';
export function menuKeySelector<T>(key: string, selector): MemoizedSelector<MenuState, T> { export function menuKeySelector<T>(key: string, selector): MemoizedSelector<MenuState, T> {
return createSelector(selector, (state) => { return createSelector(selector, (state) => {
@@ -98,7 +98,7 @@ export class MenuService {
switchMap((ids: string[]) => switchMap((ids: string[]) =>
observableCombineLatest(ids.map((id: string) => this.getMenuSection(menuID, id))) 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)); 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 * Check if a given menu is collapsed
* @param {MenuID} menuID The ID of the menu that is to be checked * @param {MenuID} menuID The ID of the menu that is to be checked
@@ -270,7 +278,7 @@ export class MenuService {
* @returns {Observable<boolean>} Emits true when the given section is currently active, false when the given section is currently inactive * @returns {Observable<boolean>} Emits true when the given section is currently active, false when the given section is currently inactive
*/ */
isSectionActive(menuID: MenuID, id: string): Observable<boolean> { isSectionActive(menuID: MenuID, id: string): Observable<boolean> {
return this.getMenuSection(menuID, id).pipe(map((section) => section.active)); return this.getMenuSection(menuID, id).pipe(hasValueOperator(), map((section) => section.active));
} }
/** /**