mirror of
https://github.com/DSpace/dspace-angular.git
synced 2025-10-08 10:34:15 +00:00
finished dynamic menus
This commit is contained in:
@@ -242,8 +242,15 @@
|
|||||||
"registries_metadata": "Metadata",
|
"registries_metadata": "Metadata",
|
||||||
"registries_format": "Format",
|
"registries_format": "Format",
|
||||||
"curation_task": "Curation Task",
|
"curation_task": "Curation Task",
|
||||||
"statistics": "Statistics Task",
|
"statistics_task": "Statistics Task",
|
||||||
"control_panel": "Control Panel"
|
"control_panel": "Control Panel",
|
||||||
|
|
||||||
|
|
||||||
|
"browse_global": "All of DSpace",
|
||||||
|
"browse_global_communities_and_collections": "Communities & Collections",
|
||||||
|
"browse_global_by_issue_date": "By Issue Date",
|
||||||
|
"browse_global_by_author": "By Author",
|
||||||
|
"statistics": "Statistics"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@@ -5,7 +5,7 @@
|
|||||||
<div class="sidebar-collapsible">
|
<div class="sidebar-collapsible">
|
||||||
<span class="section-header-text">
|
<span class="section-header-text">
|
||||||
<ng-container
|
<ng-container
|
||||||
*ngComponentOutlet="getMenuItemComponent(); injector: getItemModelInjector();"></ng-container>
|
*ngComponentOutlet="itemComponents.get(section.id); injector: itemInjectors.get(section.id);"></ng-container>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
@@ -1,9 +1,9 @@
|
|||||||
<nav class="navbar navbar-dark bg-dark p-0"
|
<nav @slideHorizontal class="navbar navbar-dark bg-dark p-0"
|
||||||
[ngClass]="{'active': sidebarOpen, 'inactive': sidebarClosed}"
|
[ngClass]="{'active': sidebarOpen, 'inactive': sidebarClosed}"
|
||||||
[@slideSidebar]="{
|
[@slideSidebar]="{
|
||||||
value: ((menuCollapsed | async) ? 'collapsed' : 'expanded'),
|
value: ((menuCollapsed | async) ? 'collapsed' : 'expanded'),
|
||||||
params: {sidebarWidth: (sidebarWidth | async)}
|
params: {sidebarWidth: (sidebarWidth | async)}
|
||||||
}" (@slideSidebar.done)="finishSlide($event)" (@slideSidebar.start)="startSlide($event)">
|
}" (@slideSidebar.done)="finishSlide($event)" (@slideSidebar.start)="startSlide($event)" *ngIf="menuVisible | async">
|
||||||
<div class="sidebar-top-level-items">
|
<div class="sidebar-top-level-items">
|
||||||
<ul class="navbar-nav">
|
<ul class="navbar-nav">
|
||||||
<li class="admin-menu-header sidebar-section">
|
<li class="admin-menu-header sidebar-section">
|
||||||
@@ -18,10 +18,11 @@
|
|||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
<li *ngFor="let section of (sections | async)">
|
|
||||||
|
<ng-container *ngFor="let section of (sections | async)">
|
||||||
<ng-container
|
<ng-container
|
||||||
*ngComponentOutlet="(getSectionComponent(section) | async); injector: getSectionDataInjector(section);"></ng-container>
|
*ngComponentOutlet="sectionComponents.get(section.id); injector: sectionInjectors.get(section.id);"></ng-container>
|
||||||
</li>
|
</ng-container>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<div class="navbar-nav">
|
<div class="navbar-nav">
|
||||||
|
@@ -1,18 +1,19 @@
|
|||||||
import { Component, Injector, OnInit } from '@angular/core';
|
import { Component, Injector, OnInit } from '@angular/core';
|
||||||
import { Observable } from 'rxjs/internal/Observable';
|
import { Observable } from 'rxjs/internal/Observable';
|
||||||
import { slideSidebar } from '../../shared/animations/slide';
|
import { slide, slideHorizontal, slideSidebar } from '../../shared/animations/slide';
|
||||||
import { CSSVariableService } from '../../shared/sass-helper/sass-helper.service';
|
import { CSSVariableService } from '../../shared/sass-helper/sass-helper.service';
|
||||||
import { MenuService } from '../../shared/menu/menu.service';
|
import { MenuService } from '../../shared/menu/menu.service';
|
||||||
import { MenuID, SectionType } from '../../shared/menu/initial-menus-state';
|
import { MenuID, SectionType } from '../../shared/menu/initial-menus-state';
|
||||||
import { MenuComponent } from '../../shared/menu/menu.component';
|
import { MenuComponent } from '../../shared/menu/menu.component';
|
||||||
import { TextSectionTypeModel } from '../../shared/menu/models/section-types/text.model';
|
import { TextSectionTypeModel } from '../../shared/menu/models/section-types/text.model';
|
||||||
import { LinkSectionTypeModel } from '../../shared/menu/models/section-types/link.model';
|
import { LinkSectionTypeModel } from '../../shared/menu/models/section-types/link.model';
|
||||||
|
import { AuthService } from '../../core/auth/auth.service';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'ds-admin-sidebar',
|
selector: 'ds-admin-sidebar',
|
||||||
templateUrl: './admin-sidebar.component.html',
|
templateUrl: './admin-sidebar.component.html',
|
||||||
styleUrls: ['./admin-sidebar.component.scss'],
|
styleUrls: ['./admin-sidebar.component.scss'],
|
||||||
animations: [slideSidebar]
|
animations: [slideHorizontal, slideSidebar]
|
||||||
})
|
})
|
||||||
export class AdminSidebarComponent extends MenuComponent implements OnInit {
|
export class AdminSidebarComponent extends MenuComponent implements OnInit {
|
||||||
menuID = MenuID.ADMIN;
|
menuID = MenuID.ADMIN;
|
||||||
@@ -22,14 +23,22 @@ export class AdminSidebarComponent extends MenuComponent implements OnInit {
|
|||||||
|
|
||||||
constructor(protected menuService: MenuService,
|
constructor(protected menuService: MenuService,
|
||||||
protected injector: Injector,
|
protected injector: Injector,
|
||||||
private variableService: CSSVariableService) {
|
private variableService: CSSVariableService,
|
||||||
|
private authService: AuthService
|
||||||
|
) {
|
||||||
super(menuService, injector);
|
super(menuService, injector);
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
|
this.createMenu();
|
||||||
super.ngOnInit();
|
super.ngOnInit();
|
||||||
this.sidebarWidth = this.variableService.getVariable('adminSidebarWidth');
|
this.sidebarWidth = this.variableService.getVariable('adminSidebarWidth');
|
||||||
this.createMenu();
|
this.authService.isAuthenticated()
|
||||||
|
.subscribe((loggedIn: boolean) => {
|
||||||
|
if (loggedIn) {
|
||||||
|
this.menuService.showMenu(this.menuID);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
createMenu() {
|
createMenu() {
|
||||||
@@ -39,7 +48,10 @@ export class AdminSidebarComponent extends MenuComponent implements OnInit {
|
|||||||
id: 'new',
|
id: 'new',
|
||||||
active: false,
|
active: false,
|
||||||
visible: true,
|
visible: true,
|
||||||
model: { type: SectionType.TEXT, text: 'admin.sidebar.section.new' } as TextSectionTypeModel,
|
model: {
|
||||||
|
type: SectionType.TEXT,
|
||||||
|
text: 'admin.sidebar.section.new'
|
||||||
|
} as TextSectionTypeModel,
|
||||||
icon: 'plus-circle'
|
icon: 'plus-circle'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -47,28 +59,44 @@ export class AdminSidebarComponent extends MenuComponent implements OnInit {
|
|||||||
parentID: 'new',
|
parentID: 'new',
|
||||||
active: false,
|
active: false,
|
||||||
visible: true,
|
visible: true,
|
||||||
model: { type: SectionType.LINK, text: 'admin.sidebar.section.new_community', link: '#' } as LinkSectionTypeModel,
|
model: {
|
||||||
|
type: SectionType.LINK,
|
||||||
|
text: 'admin.sidebar.section.new_community',
|
||||||
|
link: '/communities/submission'
|
||||||
|
} as LinkSectionTypeModel,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'new_collection',
|
id: 'new_collection',
|
||||||
parentID: 'new',
|
parentID: 'new',
|
||||||
active: false,
|
active: false,
|
||||||
visible: true,
|
visible: true,
|
||||||
model: { type: SectionType.LINK, text: 'admin.sidebar.section.new_collection', link: '#' } as LinkSectionTypeModel,
|
model: {
|
||||||
|
type: SectionType.LINK,
|
||||||
|
text: 'admin.sidebar.section.new_collection',
|
||||||
|
link: '/collections/submission'
|
||||||
|
} as LinkSectionTypeModel,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'new_item',
|
id: 'new_item',
|
||||||
parentID: 'new',
|
parentID: 'new',
|
||||||
active: false,
|
active: false,
|
||||||
visible: true,
|
visible: true,
|
||||||
model: { type: SectionType.LINK, text: 'admin.sidebar.section.new_item', link: '#' } as LinkSectionTypeModel,
|
model: {
|
||||||
|
type: SectionType.LINK,
|
||||||
|
text: 'admin.sidebar.section.new_item',
|
||||||
|
link: '/items/submission'
|
||||||
|
} as LinkSectionTypeModel,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'new_item_version',
|
id: 'new_item_version',
|
||||||
parentID: 'new',
|
parentID: 'new',
|
||||||
active: false,
|
active: false,
|
||||||
visible: true,
|
visible: true,
|
||||||
model: { type: SectionType.LINK, text: 'admin.sidebar.section.new_item_version', link: '#' } as LinkSectionTypeModel,
|
model: {
|
||||||
|
type: SectionType.LINK,
|
||||||
|
text: 'admin.sidebar.section.new_item_version',
|
||||||
|
link: '#'
|
||||||
|
} as LinkSectionTypeModel,
|
||||||
},
|
},
|
||||||
|
|
||||||
/* Edit */
|
/* Edit */
|
||||||
@@ -76,7 +104,10 @@ export class AdminSidebarComponent extends MenuComponent implements OnInit {
|
|||||||
id: 'edit',
|
id: 'edit',
|
||||||
active: false,
|
active: false,
|
||||||
visible: true,
|
visible: true,
|
||||||
model: { type: SectionType.TEXT, text: 'admin.sidebar.section.edit' } as TextSectionTypeModel,
|
model: {
|
||||||
|
type: SectionType.TEXT,
|
||||||
|
text: 'admin.sidebar.section.edit'
|
||||||
|
} as TextSectionTypeModel,
|
||||||
icon: 'pencil-alt'
|
icon: 'pencil-alt'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -84,21 +115,33 @@ export class AdminSidebarComponent extends MenuComponent implements OnInit {
|
|||||||
parentID: 'edit',
|
parentID: 'edit',
|
||||||
active: false,
|
active: false,
|
||||||
visible: true,
|
visible: true,
|
||||||
model: { type: SectionType.LINK, text: 'admin.sidebar.section.edit_community', link: '#' } as LinkSectionTypeModel,
|
model: {
|
||||||
|
type: SectionType.LINK,
|
||||||
|
text: 'admin.sidebar.section.edit_community',
|
||||||
|
link: '#'
|
||||||
|
} as LinkSectionTypeModel,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'edit_collection',
|
id: 'edit_collection',
|
||||||
parentID: 'edit',
|
parentID: 'edit',
|
||||||
active: false,
|
active: false,
|
||||||
visible: true,
|
visible: true,
|
||||||
model: { type: SectionType.LINK, text: 'admin.sidebar.section.edit_collection', link: '#' } as LinkSectionTypeModel,
|
model: {
|
||||||
|
type: SectionType.LINK,
|
||||||
|
text: 'admin.sidebar.section.edit_collection',
|
||||||
|
link: '#'
|
||||||
|
} as LinkSectionTypeModel,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'edit_item',
|
id: 'edit_item',
|
||||||
parentID: 'edit',
|
parentID: 'edit',
|
||||||
active: false,
|
active: false,
|
||||||
visible: true,
|
visible: true,
|
||||||
model: { type: SectionType.LINK, text: 'admin.sidebar.section.edit_item', link: '#' } as LinkSectionTypeModel,
|
model: {
|
||||||
|
type: SectionType.LINK,
|
||||||
|
text: 'admin.sidebar.section.edit_item',
|
||||||
|
link: '#'
|
||||||
|
} as LinkSectionTypeModel,
|
||||||
},
|
},
|
||||||
|
|
||||||
/* Import */
|
/* Import */
|
||||||
@@ -106,22 +149,35 @@ export class AdminSidebarComponent extends MenuComponent implements OnInit {
|
|||||||
id: 'import',
|
id: 'import',
|
||||||
active: false,
|
active: false,
|
||||||
visible: true,
|
visible: true,
|
||||||
model: { type: SectionType.TEXT, text: 'admin.sidebar.section.import' } as TextSectionTypeModel,
|
model: {
|
||||||
icon: 'sign-in-alt'
|
type: SectionType.TEXT,
|
||||||
|
text: 'admin.sidebar.section.import'
|
||||||
|
} as TextSectionTypeModel,
|
||||||
|
icon: 'sign-in-alt',
|
||||||
|
index: -1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'import_metadata',
|
id: 'import_metadata',
|
||||||
parentID: 'import',
|
parentID: 'import',
|
||||||
active: false,
|
active: false,
|
||||||
visible: true,
|
visible: true,
|
||||||
model: { type: SectionType.LINK, text: 'admin.sidebar.section.import_metadata', link: '#' } as LinkSectionTypeModel,
|
model: {
|
||||||
|
type: SectionType.LINK,
|
||||||
|
text: 'admin.sidebar.section.import_metadata',
|
||||||
|
link: '#'
|
||||||
|
} as LinkSectionTypeModel,
|
||||||
|
index: 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'import_batch',
|
id: 'import_batch',
|
||||||
parentID: 'import',
|
parentID: 'import',
|
||||||
active: false,
|
active: false,
|
||||||
visible: true,
|
visible: true,
|
||||||
model: { type: SectionType.LINK, text: 'admin.sidebar.section.import_batch', link: '#' } as LinkSectionTypeModel,
|
model: {
|
||||||
|
type: SectionType.LINK,
|
||||||
|
text: 'admin.sidebar.section.import_batch',
|
||||||
|
link: '#'
|
||||||
|
} as LinkSectionTypeModel,
|
||||||
},
|
},
|
||||||
|
|
||||||
/* Export */
|
/* Export */
|
||||||
@@ -129,7 +185,10 @@ export class AdminSidebarComponent extends MenuComponent implements OnInit {
|
|||||||
id: 'export',
|
id: 'export',
|
||||||
active: false,
|
active: false,
|
||||||
visible: true,
|
visible: true,
|
||||||
model: { type: SectionType.TEXT, text: 'admin.sidebar.section.export' } as TextSectionTypeModel,
|
model: {
|
||||||
|
type: SectionType.TEXT,
|
||||||
|
text: 'admin.sidebar.section.export'
|
||||||
|
} as TextSectionTypeModel,
|
||||||
icon: 'sign-out-alt'
|
icon: 'sign-out-alt'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -137,27 +196,43 @@ export class AdminSidebarComponent extends MenuComponent implements OnInit {
|
|||||||
parentID: 'export',
|
parentID: 'export',
|
||||||
active: false,
|
active: false,
|
||||||
visible: true,
|
visible: true,
|
||||||
model: { type: SectionType.LINK, text: 'admin.sidebar.section.export_community', link: '#' } as LinkSectionTypeModel,
|
model: {
|
||||||
|
type: SectionType.LINK,
|
||||||
|
text: 'admin.sidebar.section.export_community',
|
||||||
|
link: '#'
|
||||||
|
} as LinkSectionTypeModel,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'export_collection',
|
id: 'export_collection',
|
||||||
parentID: 'export',
|
parentID: 'export',
|
||||||
active: false,
|
active: false,
|
||||||
visible: true,
|
visible: true,
|
||||||
model: { type: SectionType.LINK, text: 'admin.sidebar.section.export_collection', link: '#' } as LinkSectionTypeModel,
|
model: {
|
||||||
|
type: SectionType.LINK,
|
||||||
|
text: 'admin.sidebar.section.export_collection',
|
||||||
|
link: '#'
|
||||||
|
} as LinkSectionTypeModel,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'export_item',
|
id: 'export_item',
|
||||||
parentID: 'export',
|
parentID: 'export',
|
||||||
active: false,
|
active: false,
|
||||||
visible: true,
|
visible: true,
|
||||||
model: { type: SectionType.LINK, text: 'admin.sidebar.section.export_item', link: '#' } as LinkSectionTypeModel,
|
model: {
|
||||||
|
type: SectionType.LINK,
|
||||||
|
text: 'admin.sidebar.section.export_item',
|
||||||
|
link: '#'
|
||||||
|
} as LinkSectionTypeModel,
|
||||||
}, {
|
}, {
|
||||||
id: 'export_metadata',
|
id: 'export_metadata',
|
||||||
parentID: 'export',
|
parentID: 'export',
|
||||||
active: false,
|
active: false,
|
||||||
visible: true,
|
visible: true,
|
||||||
model: { type: SectionType.LINK, text: 'admin.sidebar.section.export_metadata', link: '#' } as LinkSectionTypeModel,
|
model: {
|
||||||
|
type: SectionType.LINK,
|
||||||
|
text: 'admin.sidebar.section.export_metadata',
|
||||||
|
link: '#'
|
||||||
|
} as LinkSectionTypeModel,
|
||||||
},
|
},
|
||||||
|
|
||||||
/* Access Control */
|
/* Access Control */
|
||||||
@@ -165,7 +240,10 @@ export class AdminSidebarComponent extends MenuComponent implements OnInit {
|
|||||||
id: 'access_control',
|
id: 'access_control',
|
||||||
active: false,
|
active: false,
|
||||||
visible: true,
|
visible: true,
|
||||||
model: { type: SectionType.TEXT, text: 'admin.sidebar.section.access_control' } as TextSectionTypeModel,
|
model: {
|
||||||
|
type: SectionType.TEXT,
|
||||||
|
text: 'admin.sidebar.section.access_control'
|
||||||
|
} as TextSectionTypeModel,
|
||||||
icon: 'key'
|
icon: 'key'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -173,21 +251,33 @@ export class AdminSidebarComponent extends MenuComponent implements OnInit {
|
|||||||
parentID: 'access_control',
|
parentID: 'access_control',
|
||||||
active: false,
|
active: false,
|
||||||
visible: true,
|
visible: true,
|
||||||
model: { type: SectionType.LINK, text: 'admin.sidebar.section.access_control_people', link: '#' } as LinkSectionTypeModel,
|
model: {
|
||||||
|
type: SectionType.LINK,
|
||||||
|
text: 'admin.sidebar.section.access_control_people',
|
||||||
|
link: '#'
|
||||||
|
} as LinkSectionTypeModel,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'access_control_groups',
|
id: 'access_control_groups',
|
||||||
parentID: 'access_control',
|
parentID: 'access_control',
|
||||||
active: false,
|
active: false,
|
||||||
visible: true,
|
visible: true,
|
||||||
model: { type: SectionType.LINK, text: 'admin.sidebar.section.access_control_groups', link: '#' } as LinkSectionTypeModel,
|
model: {
|
||||||
|
type: SectionType.LINK,
|
||||||
|
text: 'admin.sidebar.section.access_control_groups',
|
||||||
|
link: '#'
|
||||||
|
} as LinkSectionTypeModel,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'access_control_authorizations',
|
id: 'access_control_authorizations',
|
||||||
parentID: 'access_control',
|
parentID: 'access_control',
|
||||||
active: false,
|
active: false,
|
||||||
visible: true,
|
visible: true,
|
||||||
model: { type: SectionType.LINK, text: 'admin.sidebar.section.access_control_authorizations', link: '#' } as LinkSectionTypeModel,
|
model: {
|
||||||
|
type: SectionType.LINK,
|
||||||
|
text: 'admin.sidebar.section.access_control_authorizations',
|
||||||
|
link: '#'
|
||||||
|
} as LinkSectionTypeModel,
|
||||||
},
|
},
|
||||||
|
|
||||||
/* Search */
|
/* Search */
|
||||||
@@ -195,7 +285,10 @@ export class AdminSidebarComponent extends MenuComponent implements OnInit {
|
|||||||
id: 'find',
|
id: 'find',
|
||||||
active: false,
|
active: false,
|
||||||
visible: true,
|
visible: true,
|
||||||
model: { type: SectionType.TEXT, text: 'admin.sidebar.section.find' } as TextSectionTypeModel,
|
model: {
|
||||||
|
type: SectionType.TEXT,
|
||||||
|
text: 'admin.sidebar.section.find'
|
||||||
|
} as TextSectionTypeModel,
|
||||||
icon: 'search'
|
icon: 'search'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -203,21 +296,33 @@ export class AdminSidebarComponent extends MenuComponent implements OnInit {
|
|||||||
parentID: 'find',
|
parentID: 'find',
|
||||||
active: false,
|
active: false,
|
||||||
visible: true,
|
visible: true,
|
||||||
model: { type: SectionType.LINK, text: 'admin.sidebar.section.find_items', link: '#' } as LinkSectionTypeModel,
|
model: {
|
||||||
|
type: SectionType.LINK,
|
||||||
|
text: 'admin.sidebar.section.find_items',
|
||||||
|
link: '/search'
|
||||||
|
} as LinkSectionTypeModel,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'find_withdrawn_items',
|
id: 'find_withdrawn_items',
|
||||||
parentID: 'find',
|
parentID: 'find',
|
||||||
active: false,
|
active: false,
|
||||||
visible: true,
|
visible: true,
|
||||||
model: { type: SectionType.LINK, text: 'admin.sidebar.section.find_withdrawn_items', link: '#' } as LinkSectionTypeModel,
|
model: {
|
||||||
|
type: SectionType.LINK,
|
||||||
|
text: 'admin.sidebar.section.find_withdrawn_items',
|
||||||
|
link: '#'
|
||||||
|
} as LinkSectionTypeModel,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'find_private_items',
|
id: 'find_private_items',
|
||||||
parentID: 'find',
|
parentID: 'find',
|
||||||
active: false,
|
active: false,
|
||||||
visible: true,
|
visible: true,
|
||||||
model: { type: SectionType.LINK, text: 'admin.sidebar.section.find_private_items', link: '#' } as LinkSectionTypeModel,
|
model: {
|
||||||
|
type: SectionType.LINK,
|
||||||
|
text: 'admin.sidebar.section.find_private_items',
|
||||||
|
link: '/admin/items'
|
||||||
|
} as LinkSectionTypeModel,
|
||||||
},
|
},
|
||||||
|
|
||||||
/* Registries */
|
/* Registries */
|
||||||
@@ -225,7 +330,10 @@ export class AdminSidebarComponent extends MenuComponent implements OnInit {
|
|||||||
id: 'registries',
|
id: 'registries',
|
||||||
active: false,
|
active: false,
|
||||||
visible: true,
|
visible: true,
|
||||||
model: { type: SectionType.TEXT, text: 'admin.sidebar.section.registries' } as TextSectionTypeModel,
|
model: {
|
||||||
|
type: SectionType.TEXT,
|
||||||
|
text: 'admin.sidebar.section.registries'
|
||||||
|
} as TextSectionTypeModel,
|
||||||
icon: 'list'
|
icon: 'list'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -233,14 +341,22 @@ export class AdminSidebarComponent extends MenuComponent implements OnInit {
|
|||||||
parentID: 'registries',
|
parentID: 'registries',
|
||||||
active: false,
|
active: false,
|
||||||
visible: true,
|
visible: true,
|
||||||
model: { type: SectionType.LINK, text: 'admin.sidebar.section.registries_metadata', link: '#' } as LinkSectionTypeModel,
|
model: {
|
||||||
|
type: SectionType.LINK,
|
||||||
|
text: 'admin.sidebar.section.registries_metadata',
|
||||||
|
link: '/registries/metadata'
|
||||||
|
} as LinkSectionTypeModel,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'registries_format',
|
id: 'registries_format',
|
||||||
parentID: 'registries',
|
parentID: 'registries',
|
||||||
active: false,
|
active: false,
|
||||||
visible: true,
|
visible: true,
|
||||||
model: { type: SectionType.LINK, text: 'admin.sidebar.section.registries_format', link: '#' } as LinkSectionTypeModel,
|
model: {
|
||||||
|
type: SectionType.LINK,
|
||||||
|
text: 'admin.sidebar.section.registries_format',
|
||||||
|
link: '/registries/format'
|
||||||
|
} as LinkSectionTypeModel,
|
||||||
},
|
},
|
||||||
|
|
||||||
/* Curation tasks */
|
/* Curation tasks */
|
||||||
@@ -248,16 +364,24 @@ export class AdminSidebarComponent extends MenuComponent implements OnInit {
|
|||||||
id: 'curation_tasks',
|
id: 'curation_tasks',
|
||||||
active: false,
|
active: false,
|
||||||
visible: true,
|
visible: true,
|
||||||
model: { type: SectionType.LINK, text: 'admin.sidebar.section.curation_task', link: '#' } as LinkSectionTypeModel,
|
model: {
|
||||||
|
type: SectionType.LINK,
|
||||||
|
text: 'admin.sidebar.section.curation_task',
|
||||||
|
link: '/curation'
|
||||||
|
} as LinkSectionTypeModel,
|
||||||
icon: 'filter'
|
icon: 'filter'
|
||||||
},
|
},
|
||||||
|
|
||||||
/* Statistics */
|
/* Statistics */
|
||||||
{
|
{
|
||||||
id: 'statistics',
|
id: 'statistics_task',
|
||||||
active: false,
|
active: false,
|
||||||
visible: true,
|
visible: true,
|
||||||
model: { type: SectionType.LINK, text: 'admin.sidebar.section.statistics', link: '#' } as LinkSectionTypeModel,
|
model: {
|
||||||
|
type: SectionType.LINK,
|
||||||
|
text: 'admin.sidebar.section.statistics_task',
|
||||||
|
link: '#'
|
||||||
|
} as LinkSectionTypeModel,
|
||||||
icon: 'chart-bar'
|
icon: 'chart-bar'
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -266,7 +390,11 @@ export class AdminSidebarComponent extends MenuComponent implements OnInit {
|
|||||||
id: 'control_panel',
|
id: 'control_panel',
|
||||||
active: false,
|
active: false,
|
||||||
visible: true,
|
visible: true,
|
||||||
model: { type: SectionType.LINK, text: 'admin.sidebar.section.control_panel', link: '#' } as LinkSectionTypeModel,
|
model: {
|
||||||
|
type: SectionType.LINK,
|
||||||
|
text: 'admin.sidebar.section.control_panel',
|
||||||
|
link: '#'
|
||||||
|
} as LinkSectionTypeModel,
|
||||||
icon: 'cogs'
|
icon: 'cogs'
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
@@ -297,4 +425,5 @@ export class AdminSidebarComponent extends MenuComponent implements OnInit {
|
|||||||
this.sidebarOpen = true;
|
this.sidebarOpen = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -10,15 +10,17 @@
|
|||||||
<div class="sidebar-collapsible">
|
<div class="sidebar-collapsible">
|
||||||
<a class="nav-item nav-link" href="#"
|
<a class="nav-item nav-link" href="#"
|
||||||
(click)="toggleSection($event)">
|
(click)="toggleSection($event)">
|
||||||
<span class="section-header-text">{{count | async}}<ng-container
|
<span class="section-header-text">
|
||||||
*ngComponentOutlet="getMenuItemComponent(); injector: getItemModelInjector();"></ng-container></span>
|
<ng-container
|
||||||
|
*ngComponentOutlet="itemComponents.get(section.id); injector: itemInjectors.get(section.id);"></ng-container>
|
||||||
|
</span>
|
||||||
<i class="fas fa-chevron-right fa-pull-right"
|
<i class="fas fa-chevron-right fa-pull-right"
|
||||||
[@rotate]="(active | async) ? 'expanded' : 'collapsed'"></i>
|
[@rotate]="(active | async) ? 'expanded' : 'collapsed'"></i>
|
||||||
</a>
|
</a>
|
||||||
<ul class="sidebar-sub-level-items list-unstyled" @slide *ngIf="(active | async)">
|
<ul class="sidebar-sub-level-items list-unstyled" @slide *ngIf="(active | async)">
|
||||||
<li class="nav-item nav-link" *ngFor="let subSection of (subSections | async)">
|
<li *ngFor="let subSection of (subSections | async)">
|
||||||
<ng-container
|
<ng-container
|
||||||
*ngComponentOutlet="getMenuItemComponent(subSection.model); injector: getItemModelInjector(subSection.model);"></ng-container>
|
*ngComponentOutlet="itemComponents.get(subSection.id); injector: itemInjectors.get(subSection.id);"></ng-container>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
@@ -9,7 +9,6 @@ import { rendersSectionForMenu } from '../../../shared/menu/menu.decorator';
|
|||||||
import { MenuService } from '../../../shared/menu/menu.service';
|
import { MenuService } from '../../../shared/menu/menu.service';
|
||||||
import { MenuSection } from '../../../shared/menu/menu.reducer';
|
import { MenuSection } from '../../../shared/menu/menu.reducer';
|
||||||
import { Observable } from 'rxjs';
|
import { Observable } from 'rxjs';
|
||||||
import { BehaviorSubject } from 'rxjs/internal/BehaviorSubject';
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'ds-expandable-admin-sidebar-section',
|
selector: 'ds-expandable-admin-sidebar-section',
|
||||||
@@ -22,22 +21,18 @@ import { BehaviorSubject } from 'rxjs/internal/BehaviorSubject';
|
|||||||
export class ExpandableAdminSidebarSectionComponent extends AdminSidebarSectionComponent implements OnInit {
|
export class ExpandableAdminSidebarSectionComponent extends AdminSidebarSectionComponent implements OnInit {
|
||||||
subSections: Observable<MenuSection[]>;
|
subSections: Observable<MenuSection[]>;
|
||||||
menuID = MenuID.ADMIN;
|
menuID = MenuID.ADMIN;
|
||||||
link = '#';
|
|
||||||
sidebarActiveBg;
|
sidebarActiveBg;
|
||||||
sidebarCollapsed: Observable<boolean>;
|
sidebarCollapsed: Observable<boolean>;
|
||||||
c = 0;
|
|
||||||
count = new BehaviorSubject<number>(0);
|
|
||||||
|
|
||||||
constructor(@Inject('sectionDataProvider') menuSection, protected menuService: MenuService,
|
constructor(@Inject('sectionDataProvider') menuSection, protected menuService: MenuService,
|
||||||
private variableService: CSSVariableService, protected injector: Injector,) {
|
private variableService: CSSVariableService, protected injector: Injector) {
|
||||||
super(menuSection, menuService, injector);
|
super(menuSection, menuService, injector);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
super.ngOnInit();
|
super.ngOnInit();
|
||||||
this.subSections = this.menuService.getSubSectionsByParentID(this.menuID, this.section.id);
|
this.subSections = this.menuService.getSubSectionsByParentID(this.menuID, this.section.id);
|
||||||
// this.active.subscribe((t) => console.log('section: ', this.section.id, this.c, t));
|
|
||||||
// this.active.subscribe((t) => this.count.next(++this.c));
|
|
||||||
this.sidebarActiveBg = this.variableService.getVariable('adminSidebarActiveBg');
|
this.sidebarActiveBg = this.variableService.getVariable('adminSidebarActiveBg');
|
||||||
this.sidebarCollapsed = this.menuService.isMenuCollapsed(this.menuID);
|
this.sidebarCollapsed = this.menuService.isMenuCollapsed(this.menuID);
|
||||||
}
|
}
|
||||||
|
@@ -31,11 +31,11 @@ import { DSpaceRouterStateSerializer } from './shared/ngrx/dspace-router-state-s
|
|||||||
import { NotificationsBoardComponent } from './shared/notifications/notifications-board/notifications-board.component';
|
import { NotificationsBoardComponent } from './shared/notifications/notifications-board/notifications-board.component';
|
||||||
import { NotificationComponent } from './shared/notifications/notification/notification.component';
|
import { NotificationComponent } from './shared/notifications/notification/notification.component';
|
||||||
import { SharedModule } from './shared/shared.module';
|
import { SharedModule } from './shared/shared.module';
|
||||||
import { NavbarComponent } from './navbar/navbar.component';
|
|
||||||
import { HeaderNavbarWrapperComponent } from './header-nav-wrapper/header-navbar-wrapper.component';
|
import { HeaderNavbarWrapperComponent } from './header-nav-wrapper/header-navbar-wrapper.component';
|
||||||
import { AdminSidebarComponent } from './+admin/admin-sidebar/admin-sidebar.component';
|
import { AdminSidebarComponent } from './+admin/admin-sidebar/admin-sidebar.component';
|
||||||
import { AdminSidebarSectionComponent } from './+admin/admin-sidebar/admin-sidebar-section/admin-sidebar-section.component';
|
import { AdminSidebarSectionComponent } from './+admin/admin-sidebar/admin-sidebar-section/admin-sidebar-section.component';
|
||||||
import { ExpandableAdminSidebarSectionComponent } from './+admin/admin-sidebar/expandable-admin-sidebar-section/expandable-admin-sidebar-section.component';
|
import { ExpandableAdminSidebarSectionComponent } from './+admin/admin-sidebar/expandable-admin-sidebar-section/expandable-admin-sidebar-section.component';
|
||||||
|
import { NavbarModule } from './navbar/navbar.module';
|
||||||
|
|
||||||
export function getConfig() {
|
export function getConfig() {
|
||||||
return ENV_CONFIG;
|
return ENV_CONFIG;
|
||||||
@@ -53,6 +53,7 @@ export function getMetaReducers(config: GlobalConfig): Array<MetaReducer<AppStat
|
|||||||
const IMPORTS = [
|
const IMPORTS = [
|
||||||
CommonModule,
|
CommonModule,
|
||||||
SharedModule,
|
SharedModule,
|
||||||
|
NavbarModule,
|
||||||
HttpClientModule,
|
HttpClientModule,
|
||||||
AppRoutingModule,
|
AppRoutingModule,
|
||||||
CoreModule.forRoot(),
|
CoreModule.forRoot(),
|
||||||
@@ -93,7 +94,6 @@ const PROVIDERS = [
|
|||||||
const DECLARATIONS = [
|
const DECLARATIONS = [
|
||||||
AppComponent,
|
AppComponent,
|
||||||
HeaderComponent,
|
HeaderComponent,
|
||||||
NavbarComponent,
|
|
||||||
HeaderNavbarWrapperComponent,
|
HeaderNavbarWrapperComponent,
|
||||||
AdminSidebarComponent,
|
AdminSidebarComponent,
|
||||||
AdminSidebarSectionComponent,
|
AdminSidebarSectionComponent,
|
||||||
@@ -122,7 +122,6 @@ const EXPORTS = [
|
|||||||
...EXPORTS
|
...EXPORTS
|
||||||
],
|
],
|
||||||
entryComponents: [
|
entryComponents: [
|
||||||
AdminSidebarComponent,
|
|
||||||
AdminSidebarSectionComponent,
|
AdminSidebarSectionComponent,
|
||||||
ExpandableAdminSidebarSectionComponent
|
ExpandableAdminSidebarSectionComponent
|
||||||
]
|
]
|
||||||
|
@@ -15,7 +15,6 @@ import {
|
|||||||
NotificationsState
|
NotificationsState
|
||||||
} from './shared/notifications/notifications.reducers';
|
} from './shared/notifications/notifications.reducers';
|
||||||
import { truncatableReducer, TruncatablesState } from './shared/truncatable/truncatable.reducer';
|
import { truncatableReducer, TruncatablesState } from './shared/truncatable/truncatable.reducer';
|
||||||
import { navbarReducer, NavbarState } from './navbar/navbar.reducer';
|
|
||||||
import { hasValue } from './shared/empty.util';
|
import { hasValue } from './shared/empty.util';
|
||||||
import { cssVariablesReducer, CSSVariablesState } from './shared/sass-helper/sass-helper.reducer';
|
import { cssVariablesReducer, CSSVariablesState } from './shared/sass-helper/sass-helper.reducer';
|
||||||
import { menusReducer, MenusState } from './shared/menu/menu.reducer';
|
import { menusReducer, MenusState } from './shared/menu/menu.reducer';
|
||||||
@@ -23,7 +22,6 @@ import { menusReducer, MenusState } from './shared/menu/menu.reducer';
|
|||||||
export interface AppState {
|
export interface AppState {
|
||||||
router: fromRouter.RouterReducerState;
|
router: fromRouter.RouterReducerState;
|
||||||
hostWindow: HostWindowState;
|
hostWindow: HostWindowState;
|
||||||
navbar: NavbarState;
|
|
||||||
forms: FormState;
|
forms: FormState;
|
||||||
notifications: NotificationsState;
|
notifications: NotificationsState;
|
||||||
searchSidebar: SearchSidebarState;
|
searchSidebar: SearchSidebarState;
|
||||||
@@ -36,7 +34,6 @@ export interface AppState {
|
|||||||
export const appReducers: ActionReducerMap<AppState> = {
|
export const appReducers: ActionReducerMap<AppState> = {
|
||||||
router: fromRouter.routerReducer,
|
router: fromRouter.routerReducer,
|
||||||
hostWindow: hostWindowReducer,
|
hostWindow: hostWindowReducer,
|
||||||
navbar: navbarReducer,
|
|
||||||
forms: formReducer,
|
forms: formReducer,
|
||||||
notifications: notificationsReducer,
|
notifications: notificationsReducer,
|
||||||
searchSidebar: sidebarReducer,
|
searchSidebar: sidebarReducer,
|
||||||
|
@@ -20,6 +20,8 @@ describe('requestReducer', () => {
|
|||||||
const testState: IndexState = {
|
const testState: IndexState = {
|
||||||
[IndexName.OBJECT]: {
|
[IndexName.OBJECT]: {
|
||||||
[key1]: val1
|
[key1]: val1
|
||||||
|
},
|
||||||
|
[IndexName.REQUEST]: {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
deepFreeze(testState);
|
deepFreeze(testState);
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
<div [ngClass]="{'open': !(isNavBarCollapsed | async)}">
|
<div [ngClass]="{'open': !(isNavBarCollapsed | async)}">
|
||||||
<ds-header></ds-header>
|
<ds-header></ds-header>
|
||||||
<ds-navbar [isNavBarCollapsed]="isNavBarCollapsed"></ds-navbar>
|
<ds-navbar></ds-navbar>
|
||||||
</div>
|
</div>
|
@@ -1,13 +1,12 @@
|
|||||||
import { Component, HostBinding, OnDestroy, OnInit } from '@angular/core';
|
import { Component, HostBinding, OnDestroy, OnInit } from '@angular/core';
|
||||||
import { createSelector, select, Store } from '@ngrx/store';
|
import { Store } from '@ngrx/store';
|
||||||
import { AppState } from '../app.reducer';
|
import { AppState } from '../app.reducer';
|
||||||
import { NavbarState } from '../navbar/navbar.reducer';
|
|
||||||
import { hasValue } from '../shared/empty.util';
|
import { hasValue } from '../shared/empty.util';
|
||||||
import { Observable } from 'rxjs/internal/Observable';
|
import { Observable } from 'rxjs/internal/Observable';
|
||||||
import { Subscription } from 'rxjs/internal/Subscription';
|
import { Subscription } from 'rxjs/internal/Subscription';
|
||||||
|
import { MenuService } from '../shared/menu/menu.service';
|
||||||
|
import { MenuID } from '../shared/menu/initial-menus-state';
|
||||||
|
|
||||||
const navbarStateSelector = (state: AppState) => state.navbar;
|
|
||||||
const navCollapsedSelector = createSelector(navbarStateSelector, (navbar: NavbarState) => navbar.navCollapsed);
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'ds-header-navbar-wrapper',
|
selector: 'ds-header-navbar-wrapper',
|
||||||
styleUrls: ['header-navbar-wrapper.component.scss'],
|
styleUrls: ['header-navbar-wrapper.component.scss'],
|
||||||
@@ -17,14 +16,16 @@ export class HeaderNavbarWrapperComponent implements OnInit, OnDestroy {
|
|||||||
@HostBinding('class.open') isOpen = false;
|
@HostBinding('class.open') isOpen = false;
|
||||||
private sub: Subscription;
|
private sub: Subscription;
|
||||||
public isNavBarCollapsed: Observable<boolean>;
|
public isNavBarCollapsed: Observable<boolean>;
|
||||||
|
menuID = MenuID.PUBLIC;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private store: Store<AppState>
|
private store: Store<AppState>,
|
||||||
|
private menuService: MenuService
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
this.isNavBarCollapsed = this.store.pipe(select(navCollapsedSelector));
|
this.isNavBarCollapsed = this.menuService.isMenuCollapsed(this.menuID);
|
||||||
this.sub = this.isNavBarCollapsed.subscribe((isCollapsed) => this.isOpen = !isCollapsed)
|
this.sub = this.isNavBarCollapsed.subscribe((isCollapsed) => this.isOpen = !isCollapsed)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -9,7 +9,7 @@
|
|||||||
<a href="#" class="px-1"><i class="fas fa-globe-asia fa-lg fa-fw"></i></a>
|
<a href="#" class="px-1"><i class="fas fa-globe-asia fa-lg fa-fw"></i></a>
|
||||||
<ds-auth-nav-menu></ds-auth-nav-menu>
|
<ds-auth-nav-menu></ds-auth-nav-menu>
|
||||||
<div class="pl-2">
|
<div class="pl-2">
|
||||||
<button class="navbar-toggler" type="button" (click)="toggle()"
|
<button class="navbar-toggler" type="button" (click)="toggleNavbar()"
|
||||||
aria-controls="collapsingNav"
|
aria-controls="collapsingNav"
|
||||||
aria-expanded="false" aria-label="Toggle navigation">
|
aria-expanded="false" aria-label="Toggle navigation">
|
||||||
<span class="navbar-toggler-icon fas fa-bars fa-fw" aria-hidden="true"></span>
|
<span class="navbar-toggler-icon fas fa-bars fa-fw" aria-hidden="true"></span>
|
||||||
|
@@ -1,8 +1,7 @@
|
|||||||
import { Component } from '@angular/core';
|
import { Component } from '@angular/core';
|
||||||
import { NavbarToggleAction } from '../navbar/navbar.actions';
|
|
||||||
import { Store } from '@ngrx/store';
|
|
||||||
import { AppState } from '../app.reducer';
|
|
||||||
import { Observable } from 'rxjs/internal/Observable';
|
import { Observable } from 'rxjs/internal/Observable';
|
||||||
|
import { MenuService } from '../shared/menu/menu.service';
|
||||||
|
import { MenuID } from '../shared/menu/initial-menus-state';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'ds-header',
|
selector: 'ds-header',
|
||||||
@@ -16,13 +15,14 @@ export class HeaderComponent {
|
|||||||
*/
|
*/
|
||||||
public isAuthenticated: Observable<boolean>;
|
public isAuthenticated: Observable<boolean>;
|
||||||
public showAuth = false;
|
public showAuth = false;
|
||||||
|
menuID = MenuID.PUBLIC;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private store: Store<AppState>,
|
private menuService: MenuService
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
|
|
||||||
public toggle(): void {
|
public toggleNavbar(): void {
|
||||||
this.store.dispatch(new NavbarToggleAction());
|
this.menuService.toggleMenu(this.menuID);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -0,0 +1,17 @@
|
|||||||
|
<li class="nav-item dropdown"
|
||||||
|
(mouseenter)="activateSection($event)"
|
||||||
|
(mouseleave)="deactivateSection($event)">
|
||||||
|
<a href="#" class="nav-link dropdown-toggle" routerLinkActive="active"
|
||||||
|
id="browseDropdown" (click)="toggleSection($event)"
|
||||||
|
data-toggle="dropdown">
|
||||||
|
<ng-container
|
||||||
|
*ngComponentOutlet="itemComponents.get(section.id); injector: itemInjectors.get(section.id);"></ng-container>
|
||||||
|
</a>
|
||||||
|
<ul @slide *ngIf="(active | async)"
|
||||||
|
class="m-0 shadow-none border-top-0 dropdown-menu show">
|
||||||
|
<ng-container *ngFor="let subSection of (subSections | async)">
|
||||||
|
<ng-container
|
||||||
|
*ngComponentOutlet="itemComponents.get(subSection.id); injector: itemInjectors.get(subSection.id);"></ng-container>
|
||||||
|
</ng-container>
|
||||||
|
</ul>
|
||||||
|
</li>
|
@@ -0,0 +1,27 @@
|
|||||||
|
@import '../../../styles/variables.scss';
|
||||||
|
|
||||||
|
.dropdown-menu {
|
||||||
|
overflow: hidden;
|
||||||
|
min-width: 100%;
|
||||||
|
border-top-left-radius: 0;
|
||||||
|
border-top-right-radius: 0;
|
||||||
|
::ng-deep a.nav-link {
|
||||||
|
padding-right: $spacer;
|
||||||
|
padding-left: $spacer;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Mobile menu styling **/
|
||||||
|
@media screen and (max-width: map-get($grid-breakpoints, md)) {
|
||||||
|
.dropdown-toggle {
|
||||||
|
&:after {
|
||||||
|
float: right;
|
||||||
|
margin-top: $spacer/2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.dropdown-menu {
|
||||||
|
border: 0;
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,25 @@
|
|||||||
|
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { ExpandableNavbarSectionComponent } from './expandable-navbar-section.component';
|
||||||
|
|
||||||
|
describe('ExpandableNavbarSectionComponent', () => {
|
||||||
|
let component: ExpandableNavbarSectionComponent;
|
||||||
|
let fixture: ComponentFixture<ExpandableNavbarSectionComponent>;
|
||||||
|
|
||||||
|
beforeEach(async(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
declarations: [ ExpandableNavbarSectionComponent ]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
}));
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(ExpandableNavbarSectionComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
@@ -0,0 +1,66 @@
|
|||||||
|
import { Component, Inject, Injector, OnInit } from '@angular/core';
|
||||||
|
import { NavbarSectionComponent } from '../navbar-section/navbar-section.component';
|
||||||
|
import { MenuService } from '../../shared/menu/menu.service';
|
||||||
|
import { MenuID } from '../../shared/menu/initial-menus-state';
|
||||||
|
import { rendersSectionForMenu } from '../../shared/menu/menu.decorator';
|
||||||
|
import { Observable } from 'rxjs/internal/Observable';
|
||||||
|
import { MenuSection } from '../../shared/menu/menu.reducer';
|
||||||
|
import { slide } from '../../shared/animations/slide';
|
||||||
|
import { first } from 'rxjs/operators';
|
||||||
|
import { HostWindowService } from '../../shared/host-window.service';
|
||||||
|
import { GenericConstructor } from '../../core/shared/generic-constructor';
|
||||||
|
import { MenuSectionComponent } from '../../shared/menu/menu-section/menu-section.component';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'ds-expandable-navbar-section',
|
||||||
|
templateUrl: './expandable-navbar-section.component.html',
|
||||||
|
styleUrls: ['./expandable-navbar-section.component.scss'],
|
||||||
|
animations: [slide]
|
||||||
|
})
|
||||||
|
@rendersSectionForMenu(MenuID.PUBLIC, true)
|
||||||
|
export class ExpandableNavbarSectionComponent extends NavbarSectionComponent implements OnInit {
|
||||||
|
menuID = MenuID.PUBLIC;
|
||||||
|
|
||||||
|
constructor(@Inject('sectionDataProvider') menuSection,
|
||||||
|
protected menuService: MenuService,
|
||||||
|
protected injector: Injector,
|
||||||
|
private windowService: HostWindowService
|
||||||
|
) {
|
||||||
|
super(menuSection, menuService, injector);
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnInit() {
|
||||||
|
super.ngOnInit();
|
||||||
|
}
|
||||||
|
|
||||||
|
activateSection(event): void {
|
||||||
|
this.windowService.isXsOrSm().pipe(
|
||||||
|
first()
|
||||||
|
).subscribe((isMobile) => {
|
||||||
|
if (!isMobile) {
|
||||||
|
super.activateSection(event);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
deactivateSection(event): void {
|
||||||
|
this.windowService.isXsOrSm().pipe(
|
||||||
|
first()
|
||||||
|
).subscribe((isMobile) => {
|
||||||
|
if (!isMobile) {
|
||||||
|
super.deactivateSection(event);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
toggleSection(event): void {
|
||||||
|
event.preventDefault();
|
||||||
|
this.windowService.isXsOrSm().pipe(
|
||||||
|
first()
|
||||||
|
).subscribe((isMobile) => {
|
||||||
|
if (isMobile) {
|
||||||
|
super.toggleSection(event);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,4 @@
|
|||||||
|
<li class="nav-item">
|
||||||
|
<ng-container
|
||||||
|
*ngComponentOutlet="itemComponents.get(section.id); injector: itemInjectors.get(section.id);"></ng-container>
|
||||||
|
</li>
|
@@ -0,0 +1,25 @@
|
|||||||
|
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { NavbarSectionComponent } from './navbar-section.component';
|
||||||
|
|
||||||
|
describe('NavbarSectionComponent', () => {
|
||||||
|
let component: NavbarSectionComponent;
|
||||||
|
let fixture: ComponentFixture<NavbarSectionComponent>;
|
||||||
|
|
||||||
|
beforeEach(async(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
declarations: [ NavbarSectionComponent ]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
}));
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(NavbarSectionComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
27
src/app/navbar/navbar-section/navbar-section.component.ts
Normal file
27
src/app/navbar/navbar-section/navbar-section.component.ts
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
import { Component, Inject, Injector, OnInit } from '@angular/core';
|
||||||
|
import { MenuSectionComponent } from '../../shared/menu/menu-section/menu-section.component';
|
||||||
|
import { MenuService } from '../../shared/menu/menu.service';
|
||||||
|
import { MenuID } from '../../shared/menu/initial-menus-state';
|
||||||
|
import { rendersSectionForMenu } from '../../shared/menu/menu.decorator';
|
||||||
|
import { HostWindowService } from '../../shared/host-window.service';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'ds-navbar-section',
|
||||||
|
templateUrl: './navbar-section.component.html',
|
||||||
|
styleUrls: ['./navbar-section.component.scss']
|
||||||
|
})
|
||||||
|
@rendersSectionForMenu(MenuID.PUBLIC, false)
|
||||||
|
export class NavbarSectionComponent extends MenuSectionComponent implements OnInit {
|
||||||
|
menuID = MenuID.PUBLIC;
|
||||||
|
|
||||||
|
constructor(@Inject('sectionDataProvider') menuSection,
|
||||||
|
protected menuService: MenuService,
|
||||||
|
protected injector: Injector
|
||||||
|
) {
|
||||||
|
super(menuSection, menuService, injector);
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnInit() {
|
||||||
|
super.ngOnInit();
|
||||||
|
}
|
||||||
|
}
|
@@ -1,40 +0,0 @@
|
|||||||
import { Action } from '@ngrx/store';
|
|
||||||
|
|
||||||
import { type } from '../shared/ngrx/type';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* For each action type in an action group, make a simple
|
|
||||||
* enum object for all of this group's action types.
|
|
||||||
*
|
|
||||||
* The 'type' utility function coerces strings into string
|
|
||||||
* literal types and runs a simple check to guarantee all
|
|
||||||
* action types in the application are unique.
|
|
||||||
*/
|
|
||||||
export const NavbarActionTypes = {
|
|
||||||
COLLAPSE: type('dspace/navbar/COLLAPSE'),
|
|
||||||
EXPAND: type('dspace/navbar/EXPAND'),
|
|
||||||
TOGGLE: type('dspace/navbar/TOGGLE')
|
|
||||||
};
|
|
||||||
|
|
||||||
/* tslint:disable:max-classes-per-file */
|
|
||||||
export class NavbarCollapseAction implements Action {
|
|
||||||
type = NavbarActionTypes.COLLAPSE;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class NavbarExpandAction implements Action {
|
|
||||||
type = NavbarActionTypes.EXPAND;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class NavbarToggleAction implements Action {
|
|
||||||
type = NavbarActionTypes.TOGGLE;
|
|
||||||
}
|
|
||||||
/* tslint:enable:max-classes-per-file */
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Export a type alias of all actions in this action group
|
|
||||||
* so that reducers can easily compose action types
|
|
||||||
*/
|
|
||||||
export type NavbarAction
|
|
||||||
= NavbarCollapseAction
|
|
||||||
| NavbarExpandAction
|
|
||||||
| NavbarToggleAction
|
|
@@ -1,46 +1,17 @@
|
|||||||
<nav [ngClass]="{'open': !(isNavBarCollapsed | async)}"
|
<nav [ngClass]="{'open': !(menuCollapsed | async)}"
|
||||||
[@slideMobileNav]="!(windowService.isXsOrSm() | async) ? 'default' : ((isNavBarCollapsed | async) ? 'collapsed' : 'expanded')"
|
[@slideMobileNav]="!(windowService.isXsOrSm() | async) ? 'default' : ((menuCollapsed | async) ? 'collapsed' : 'expanded')"
|
||||||
class="navbar navbar-light navbar-expand-md p-md-0 navbar-container"> <!-- TODO remove navbar-container class when https://github.com/twbs/bootstrap/issues/24726 is fixed -->
|
class="navbar navbar-light navbar-expand-md p-md-0 navbar-container"> <!-- TODO remove navbar-container class when https://github.com/twbs/bootstrap/issues/24726 is fixed -->
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="reset-padding-md w-100">
|
<div class="reset-padding-md w-100">
|
||||||
<div id="collapsingNav">
|
<div id="collapsingNav">
|
||||||
<ul class="navbar-nav mr-auto shadow-none">
|
<ul class="navbar-nav mr-auto shadow-none">
|
||||||
<li ngbDropdown class="nav-item" #dropdown1="ngbDropdown"
|
<ng-container *ngFor="let section of (sections | async)">
|
||||||
(mouseenter)="openDropdownOnHover(dropdown1)"
|
<ng-container
|
||||||
(mouseleave)="closeDropdownOnHover(dropdown1)">
|
*ngComponentOutlet="sectionComponents.get(section.id); injector: sectionInjectors.get(section.id);"></ng-container>
|
||||||
<a href="#" ngbDropdownToggle class="nav-link" routerLinkActive="active"
|
</ng-container>
|
||||||
id="browseDropdown" (click)="$event.preventDefault();"
|
|
||||||
data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">{{
|
|
||||||
'nav.browse.header' | translate }}</a>
|
|
||||||
<div ngbDropdownMenu aria-labelledby="browseDropdown"
|
|
||||||
class="m-0 shadow-none border-top-0 browse-dropdown-menu">
|
|
||||||
<a class="dropdown-item" href="/communitylist">Communities &
|
|
||||||
Collections</a>
|
|
||||||
<a class="dropdown-item" href="/browse?type=dateissued">By Issue
|
|
||||||
Date</a>
|
|
||||||
<a class="dropdown-item" href="/browse?type=author">By Author</a>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
<li ngbDropdown class="nav-item" #dropdown2="ngbDropdown"
|
|
||||||
(mouseenter)="openDropdownOnHover(dropdown2)"
|
|
||||||
(mouseleave)="closeDropdownOnHover(dropdown2)">
|
|
||||||
<a href="#" ngbDropdownToggle class="nav-link" routerLinkActive="active"
|
|
||||||
id="scopeBrowseDropdown" (click)="$event.preventDefault();"
|
|
||||||
data-toggle="dropdown" aria-haspopup="true"
|
|
||||||
aria-expanded="false">{{ 'nav.community-browse.header' | translate }}</a>
|
|
||||||
<div ngbDropdownMenu aria-labelledby="scopeBrowseDropdown"
|
|
||||||
class="rounded-bottom m-0 shadow-none border-top-0">
|
|
||||||
<a class="dropdown-item" href="/browse?type=dateissued">By Issue
|
|
||||||
Date</a>
|
|
||||||
<a class="dropdown-item" href="/browse?type=author">By Author</a>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
<li class="nav-item">
|
|
||||||
<a class="nav-link" routerLink="/statistics" routerLinkActive="active">{{
|
|
||||||
'nav.statistics.header' | translate }}</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
|
@@ -5,12 +5,6 @@ nav.navbar {
|
|||||||
align-items: baseline;
|
align-items: baseline;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dropdown-menu {
|
|
||||||
min-width: 100%;
|
|
||||||
border-top-left-radius: 0;
|
|
||||||
border-top-right-radius: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Mobile menu styling **/
|
/** Mobile menu styling **/
|
||||||
@media screen and (max-width: map-get($grid-breakpoints, md)) {
|
@media screen and (max-width: map-get($grid-breakpoints, md)) {
|
||||||
.navbar {
|
.navbar {
|
||||||
@@ -23,15 +17,6 @@ nav.navbar {
|
|||||||
&.open {
|
&.open {
|
||||||
height: 100vh; //doesn't matter because wrapper is sticky
|
height: 100vh; //doesn't matter because wrapper is sticky
|
||||||
}
|
}
|
||||||
.dropdown-toggle {
|
|
||||||
&:after {
|
|
||||||
float: right;
|
|
||||||
margin-top: $spacer/2;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.dropdown-menu {
|
|
||||||
border: 0;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -15,12 +15,13 @@ import { Router } from '@angular/router';
|
|||||||
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
|
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
|
||||||
import * as ngrx from '@ngrx/store';
|
import * as ngrx from '@ngrx/store';
|
||||||
import { NO_ERRORS_SCHEMA } from '@angular/core';
|
import { NO_ERRORS_SCHEMA } from '@angular/core';
|
||||||
import { NavbarState } from './navbar.reducer';
|
import { ToggleMenuAction } from '../shared/menu/menu.actions';
|
||||||
import { NavbarToggleAction } from './navbar.actions';
|
import { MenuID } from '../shared/menu/initial-menus-state';
|
||||||
|
import { MenusState } from '../shared/menu/menu.reducer';
|
||||||
|
|
||||||
let comp: NavbarComponent;
|
let comp: NavbarComponent;
|
||||||
let fixture: ComponentFixture<NavbarComponent>;
|
let fixture: ComponentFixture<NavbarComponent>;
|
||||||
let store: Store<NavbarState>;
|
let store: Store<MenusState>;
|
||||||
|
|
||||||
describe('NavbarComponent', () => {
|
describe('NavbarComponent', () => {
|
||||||
|
|
||||||
@@ -49,7 +50,7 @@ describe('NavbarComponent', () => {
|
|||||||
|
|
||||||
comp = fixture.componentInstance;
|
comp = fixture.componentInstance;
|
||||||
|
|
||||||
store = fixture.debugElement.injector.get(Store) as Store<NavbarState>;
|
store = fixture.debugElement.injector.get(Store) as Store<MenusState>;
|
||||||
spyOn(store, 'dispatch');
|
spyOn(store, 'dispatch');
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -61,7 +62,7 @@ describe('NavbarComponent', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should dispatch a NavbarToggleAction', () => {
|
it('should dispatch a NavbarToggleAction', () => {
|
||||||
expect(store.dispatch).toHaveBeenCalledWith(new NavbarToggleAction());
|
expect(store.dispatch).toHaveBeenCalledWith(new ToggleMenuAction(MenuID.PUBLIC));
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
@@ -1,10 +1,11 @@
|
|||||||
import { Component, Input } from '@angular/core';
|
import { Component, Injector, OnInit } from '@angular/core';
|
||||||
import { Store } from '@ngrx/store';
|
|
||||||
import { AppState } from '../app.reducer';
|
|
||||||
import { slideMobileNav } from '../shared/animations/slide';
|
import { slideMobileNav } from '../shared/animations/slide';
|
||||||
|
import { MenuComponent } from '../shared/menu/menu.component';
|
||||||
|
import { MenuService } from '../shared/menu/menu.service';
|
||||||
|
import { MenuID, SectionType } from '../shared/menu/initial-menus-state';
|
||||||
|
import { TextSectionTypeModel } from '../shared/menu/models/section-types/text.model';
|
||||||
|
import { LinkSectionTypeModel } from '../shared/menu/models/section-types/link.model';
|
||||||
import { HostWindowService } from '../shared/host-window.service';
|
import { HostWindowService } from '../shared/host-window.service';
|
||||||
import { first } from 'rxjs/operators';
|
|
||||||
import { Observable } from 'rxjs/internal/Observable';
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'ds-navbar',
|
selector: 'ds-navbar',
|
||||||
@@ -12,32 +13,81 @@ import { Observable } from 'rxjs/internal/Observable';
|
|||||||
templateUrl: 'navbar.component.html',
|
templateUrl: 'navbar.component.html',
|
||||||
animations: [slideMobileNav]
|
animations: [slideMobileNav]
|
||||||
})
|
})
|
||||||
export class NavbarComponent {
|
export class NavbarComponent extends MenuComponent implements OnInit {
|
||||||
@Input() isNavBarCollapsed: Observable<boolean>;
|
menuID = MenuID.PUBLIC;
|
||||||
|
|
||||||
constructor(
|
constructor(protected menuService: MenuService,
|
||||||
private store: Store<AppState>,
|
protected injector: Injector,
|
||||||
public windowService: HostWindowService
|
public windowService: HostWindowService
|
||||||
) {
|
) {
|
||||||
|
super(menuService, injector);
|
||||||
}
|
}
|
||||||
|
|
||||||
openDropdownOnHover(dropdown: any): void {
|
ngOnInit(): void {
|
||||||
this.windowService.isXsOrSm().pipe(
|
this.createMenu();
|
||||||
first()
|
super.ngOnInit();
|
||||||
).subscribe((isMobile) => {
|
|
||||||
if (!isMobile) {
|
|
||||||
dropdown.open();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
closeDropdownOnHover(dropdown: any): void {
|
createMenu() {
|
||||||
this.windowService.isXsOrSm().pipe(
|
const menuList = [
|
||||||
first()
|
/* News */
|
||||||
).subscribe((isMobile) => {
|
{
|
||||||
if (!isMobile) {
|
id: 'browse_global',
|
||||||
dropdown.close();
|
active: false,
|
||||||
}
|
visible: true,
|
||||||
});
|
model: {
|
||||||
|
type: SectionType.TEXT,
|
||||||
|
text: 'admin.sidebar.section.browse_global'
|
||||||
|
} as TextSectionTypeModel,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'browse_global_communities_and_collections',
|
||||||
|
parentID: 'browse_global',
|
||||||
|
active: false,
|
||||||
|
visible: true,
|
||||||
|
model: {
|
||||||
|
type: SectionType.LINK,
|
||||||
|
text: 'admin.sidebar.section.browse_global_communities_and_collections',
|
||||||
|
link: '#'
|
||||||
|
} as LinkSectionTypeModel,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'browse_global_global_by_issue_date',
|
||||||
|
parentID: 'browse_global',
|
||||||
|
active: false,
|
||||||
|
visible: true,
|
||||||
|
model: {
|
||||||
|
type: SectionType.LINK,
|
||||||
|
text: 'admin.sidebar.section.browse_global_by_issue_date',
|
||||||
|
link: '#'
|
||||||
|
} as LinkSectionTypeModel,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'browse_global_by_author',
|
||||||
|
parentID: 'browse_global',
|
||||||
|
active: false,
|
||||||
|
visible: true,
|
||||||
|
model: {
|
||||||
|
type: SectionType.LINK,
|
||||||
|
text: 'admin.sidebar.section.browse_global_by_author',
|
||||||
|
link: '#'
|
||||||
|
} as LinkSectionTypeModel,
|
||||||
|
},
|
||||||
|
|
||||||
|
/* Statistics */
|
||||||
|
{
|
||||||
|
id: 'statistics',
|
||||||
|
active: false,
|
||||||
|
visible: true,
|
||||||
|
model: {
|
||||||
|
type: SectionType.LINK,
|
||||||
|
text: 'admin.sidebar.section.statistics',
|
||||||
|
link: '#'
|
||||||
|
} as LinkSectionTypeModel,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
menuList.forEach((menuSection) => this.menuService.addSection(this.menuID, menuSection));
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -1,11 +1,12 @@
|
|||||||
import { TestBed } from '@angular/core/testing';
|
import { TestBed } from '@angular/core/testing';
|
||||||
import { NavbarEffects } from './navbar.effects';
|
import { NavbarEffects } from './navbar.effects';
|
||||||
import { NavbarCollapseAction } from './navbar.actions';
|
|
||||||
import { HostWindowResizeAction } from '../shared/host-window.actions';
|
import { HostWindowResizeAction } from '../shared/host-window.actions';
|
||||||
import { Observable } from 'rxjs';
|
import { Observable } from 'rxjs';
|
||||||
import { provideMockActions } from '@ngrx/effects/testing';
|
import { provideMockActions } from '@ngrx/effects/testing';
|
||||||
import { cold, hot } from 'jasmine-marbles';
|
import { cold, hot } from 'jasmine-marbles';
|
||||||
import * as fromRouter from '@ngrx/router-store';
|
import * as fromRouter from '@ngrx/router-store';
|
||||||
|
import { CollapseMenuAction } from '../shared/menu/menu.actions';
|
||||||
|
import { MenuID } from '../shared/menu/initial-menus-state';
|
||||||
|
|
||||||
describe('NavbarEffects', () => {
|
describe('NavbarEffects', () => {
|
||||||
let navbarEffects: NavbarEffects;
|
let navbarEffects: NavbarEffects;
|
||||||
@@ -28,7 +29,7 @@ describe('NavbarEffects', () => {
|
|||||||
it('should return a COLLAPSE action in response to a RESIZE action', () => {
|
it('should return a COLLAPSE action in response to a RESIZE action', () => {
|
||||||
actions = hot('--a-', { a: new HostWindowResizeAction(800, 600) });
|
actions = hot('--a-', { a: new HostWindowResizeAction(800, 600) });
|
||||||
|
|
||||||
const expected = cold('--b-', { b: new NavbarCollapseAction() });
|
const expected = cold('--b-', { b: new CollapseMenuAction(MenuID.PUBLIC) });
|
||||||
|
|
||||||
expect(navbarEffects.resize$).toBeObservable(expected);
|
expect(navbarEffects.resize$).toBeObservable(expected);
|
||||||
});
|
});
|
||||||
@@ -40,7 +41,7 @@ describe('NavbarEffects', () => {
|
|||||||
it('should return a COLLAPSE action in response to an UPDATE_LOCATION action', () => {
|
it('should return a COLLAPSE action in response to an UPDATE_LOCATION action', () => {
|
||||||
actions = hot('--a-', { a: { type: fromRouter.ROUTER_NAVIGATION } });
|
actions = hot('--a-', { a: { type: fromRouter.ROUTER_NAVIGATION } });
|
||||||
|
|
||||||
const expected = cold('--b-', { b: new NavbarCollapseAction() });
|
const expected = cold('--b-', { b: new CollapseMenuAction(MenuID.PUBLIC) });
|
||||||
|
|
||||||
expect(navbarEffects.routeChange$).toBeObservable(expected);
|
expect(navbarEffects.routeChange$).toBeObservable(expected);
|
||||||
});
|
});
|
||||||
|
@@ -1,24 +1,25 @@
|
|||||||
import { map } from 'rxjs/operators';
|
import { map } from 'rxjs/operators';
|
||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import { Effect, Actions, ofType } from '@ngrx/effects'
|
import { Actions, Effect, ofType } from '@ngrx/effects'
|
||||||
import * as fromRouter from '@ngrx/router-store';
|
import * as fromRouter from '@ngrx/router-store';
|
||||||
|
|
||||||
import { HostWindowActionTypes } from '../shared/host-window.actions';
|
import { HostWindowActionTypes } from '../shared/host-window.actions';
|
||||||
import { NavbarCollapseAction } from './navbar.actions';
|
import { CollapseMenuAction } from '../shared/menu/menu.actions';
|
||||||
|
import { MenuID } from '../shared/menu/initial-menus-state';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class NavbarEffects {
|
export class NavbarEffects {
|
||||||
|
menuID = MenuID.PUBLIC;
|
||||||
@Effect() resize$ = this.actions$
|
@Effect() resize$ = this.actions$
|
||||||
.pipe(
|
.pipe(
|
||||||
ofType(HostWindowActionTypes.RESIZE),
|
ofType(HostWindowActionTypes.RESIZE),
|
||||||
map(() => new NavbarCollapseAction())
|
map(() => new CollapseMenuAction(this.menuID))
|
||||||
);
|
);
|
||||||
|
|
||||||
@Effect() routeChange$ = this.actions$
|
@Effect() routeChange$ = this.actions$
|
||||||
.pipe(
|
.pipe(
|
||||||
ofType(fromRouter.ROUTER_NAVIGATION),
|
ofType(fromRouter.ROUTER_NAVIGATION),
|
||||||
map(() => new NavbarCollapseAction())
|
map(() => new CollapseMenuAction(this.menuID))
|
||||||
);
|
);
|
||||||
|
|
||||||
constructor(private actions$: Actions) {
|
constructor(private actions$: Actions) {
|
||||||
|
45
src/app/navbar/navbar.module.ts
Normal file
45
src/app/navbar/navbar.module.ts
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
import { SharedModule } from '../shared/shared.module';
|
||||||
|
import { CommonModule } from '@angular/common';
|
||||||
|
import { NgModule } from '@angular/core';
|
||||||
|
import { EffectsModule } from '@ngrx/effects';
|
||||||
|
import { CoreModule } from '../core/core.module';
|
||||||
|
import { NavbarEffects } from './navbar.effects';
|
||||||
|
import { NavbarSectionComponent } from './navbar-section/navbar-section.component';
|
||||||
|
import { ExpandableNavbarSectionComponent } from './expandable-navbar-section/expandable-navbar-section.component';
|
||||||
|
import { NavbarComponent } from './navbar.component';
|
||||||
|
|
||||||
|
const effects = [
|
||||||
|
NavbarEffects
|
||||||
|
];
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
imports: [
|
||||||
|
CommonModule,
|
||||||
|
SharedModule,
|
||||||
|
EffectsModule.forFeature(effects),
|
||||||
|
CoreModule.forRoot()
|
||||||
|
],
|
||||||
|
declarations: [
|
||||||
|
NavbarComponent,
|
||||||
|
NavbarSectionComponent,
|
||||||
|
ExpandableNavbarSectionComponent
|
||||||
|
],
|
||||||
|
providers: [
|
||||||
|
|
||||||
|
],
|
||||||
|
entryComponents: [
|
||||||
|
NavbarSectionComponent,
|
||||||
|
ExpandableNavbarSectionComponent
|
||||||
|
],
|
||||||
|
exports: [
|
||||||
|
NavbarComponent,
|
||||||
|
NavbarSectionComponent,
|
||||||
|
ExpandableNavbarSectionComponent
|
||||||
|
]
|
||||||
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This module handles all components and pipes that are necessary for the search page
|
||||||
|
*/
|
||||||
|
export class NavbarModule {
|
||||||
|
}
|
@@ -1,85 +0,0 @@
|
|||||||
import * as deepFreeze from 'deep-freeze';
|
|
||||||
|
|
||||||
import { navbarReducer } from './navbar.reducer';
|
|
||||||
import { NavbarCollapseAction, NavbarExpandAction, NavbarToggleAction } from './navbar.actions';
|
|
||||||
|
|
||||||
class NullAction extends NavbarCollapseAction {
|
|
||||||
type = null;
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
super();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
describe('navbarReducer', () => {
|
|
||||||
it('should return the current state when no valid actions have been made', () => {
|
|
||||||
const state = { navCollapsed: false };
|
|
||||||
const action = new NullAction();
|
|
||||||
const newState = navbarReducer(state, action);
|
|
||||||
|
|
||||||
expect(newState).toEqual(state);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should start with navCollapsed = true', () => {
|
|
||||||
const action = new NullAction();
|
|
||||||
const initialState = navbarReducer(undefined, action);
|
|
||||||
|
|
||||||
// The navigation starts collapsed
|
|
||||||
expect(initialState.navCollapsed).toEqual(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should set navCollapsed to true in response to the COLLAPSE action', () => {
|
|
||||||
const state = { navCollapsed: false };
|
|
||||||
const action = new NavbarCollapseAction();
|
|
||||||
const newState = navbarReducer(state, action);
|
|
||||||
|
|
||||||
expect(newState.navCollapsed).toEqual(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should perform the COLLAPSE action without affecting the previous state', () => {
|
|
||||||
const state = { navCollapsed: false };
|
|
||||||
deepFreeze(state);
|
|
||||||
|
|
||||||
const action = new NavbarCollapseAction();
|
|
||||||
navbarReducer(state, action);
|
|
||||||
|
|
||||||
// no expect required, deepFreeze will ensure an exception is thrown if the state
|
|
||||||
// is mutated, and any uncaught exception will cause the test to fail
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should set navCollapsed to false in response to the EXPAND action', () => {
|
|
||||||
const state = { navCollapsed: true };
|
|
||||||
const action = new NavbarExpandAction();
|
|
||||||
const newState = navbarReducer(state, action);
|
|
||||||
|
|
||||||
expect(newState.navCollapsed).toEqual(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should perform the EXPAND action without affecting the previous state', () => {
|
|
||||||
const state = { navCollapsed: true };
|
|
||||||
deepFreeze(state);
|
|
||||||
|
|
||||||
const action = new NavbarExpandAction();
|
|
||||||
navbarReducer(state, action);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should flip the value of navCollapsed in response to the TOGGLE action', () => {
|
|
||||||
const state1 = { navCollapsed: true };
|
|
||||||
const action = new NavbarToggleAction();
|
|
||||||
|
|
||||||
const state2 = navbarReducer(state1, action);
|
|
||||||
const state3 = navbarReducer(state2, action);
|
|
||||||
|
|
||||||
expect(state2.navCollapsed).toEqual(false);
|
|
||||||
expect(state3.navCollapsed).toEqual(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should perform the TOGGLE action without affecting the previous state', () => {
|
|
||||||
const state = { navCollapsed: true };
|
|
||||||
deepFreeze(state);
|
|
||||||
|
|
||||||
const action = new NavbarToggleAction();
|
|
||||||
navbarReducer(state, action);
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
@@ -1,38 +0,0 @@
|
|||||||
import { NavbarAction, NavbarActionTypes } from './navbar.actions';
|
|
||||||
|
|
||||||
export interface NavbarState {
|
|
||||||
navCollapsed: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
const initialState: NavbarState = {
|
|
||||||
navCollapsed: true
|
|
||||||
};
|
|
||||||
|
|
||||||
export function navbarReducer(state = initialState, action: NavbarAction): NavbarState {
|
|
||||||
switch (action.type) {
|
|
||||||
|
|
||||||
case NavbarActionTypes.COLLAPSE: {
|
|
||||||
return Object.assign({}, state, {
|
|
||||||
navCollapsed: true
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
case NavbarActionTypes.EXPAND: {
|
|
||||||
return Object.assign({}, state, {
|
|
||||||
navCollapsed: false
|
|
||||||
});
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
case NavbarActionTypes.TOGGLE: {
|
|
||||||
return Object.assign({}, state, {
|
|
||||||
navCollapsed: !state.navCollapsed
|
|
||||||
});
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
default: {
|
|
||||||
return state;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,7 +1,8 @@
|
|||||||
import {
|
import {
|
||||||
animate,
|
animate,
|
||||||
animateChild,
|
animateChild,
|
||||||
group, query, sequence,
|
group,
|
||||||
|
query,
|
||||||
state,
|
state,
|
||||||
style,
|
style,
|
||||||
transition,
|
transition,
|
||||||
@@ -11,8 +12,15 @@ import {
|
|||||||
export const slide = trigger('slide', [
|
export const slide = trigger('slide', [
|
||||||
state('void', style({ height: 0 })),
|
state('void', style({ height: 0 })),
|
||||||
state('*', style({ height: '*' })),
|
state('*', style({ height: '*' })),
|
||||||
transition(':enter', [animate('2000ms')]),
|
transition(':enter', [animate('200ms')]),
|
||||||
transition(':leave', [animate('2000ms')])
|
transition(':leave', [animate('200ms')])
|
||||||
|
]);
|
||||||
|
|
||||||
|
export const slideHorizontal = trigger('slideHorizontal', [
|
||||||
|
state('void', style({ width: 0 })),
|
||||||
|
state('*', style({ width: '*' })),
|
||||||
|
transition(':enter', [animate('200ms')]),
|
||||||
|
transition(':leave', [animate('200ms')])
|
||||||
]);
|
]);
|
||||||
|
|
||||||
export const slideMobileNav = trigger('slideMobileNav', [
|
export const slideMobileNav = trigger('slideMobileNav', [
|
||||||
|
@@ -6,6 +6,8 @@ import { MenuID, SectionType } from '../initial-menus-state';
|
|||||||
import { hasNoValue, hasValue } from '../../empty.util';
|
import { hasNoValue, hasValue } from '../../empty.util';
|
||||||
import { Observable } from 'rxjs/internal/Observable';
|
import { Observable } from 'rxjs/internal/Observable';
|
||||||
import { SectionTypeModel } from '../models/section-types/section-type.model';
|
import { SectionTypeModel } from '../models/section-types/section-type.model';
|
||||||
|
import { distinctUntilChanged } from 'rxjs/operators';
|
||||||
|
import { GenericConstructor } from '../../../core/shared/generic-constructor';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'ds-menu-section',
|
selector: 'ds-menu-section',
|
||||||
@@ -14,12 +16,16 @@ import { SectionTypeModel } from '../models/section-types/section-type.model';
|
|||||||
export class MenuSectionComponent {
|
export class MenuSectionComponent {
|
||||||
active: Observable<boolean>;
|
active: Observable<boolean>;
|
||||||
menuID: MenuID;
|
menuID: MenuID;
|
||||||
|
itemInjectors: Map<string, Injector> = new Map<string, Injector>();
|
||||||
|
itemComponents: Map<string, GenericConstructor<MenuSectionComponent>> = new Map<string, GenericConstructor<MenuSectionComponent>>();
|
||||||
|
subSections: Observable<MenuSection[]>;
|
||||||
|
|
||||||
constructor(protected section: MenuSection, protected menuService: MenuService, protected injector: Injector) {
|
constructor(public section: MenuSection, protected menuService: MenuService, protected injector: Injector) {
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
this.active = this.menuService.isSectionActive(this.menuID, this.section.id);
|
this.active = this.menuService.isSectionActive(this.menuID, this.section.id).pipe(distinctUntilChanged());
|
||||||
|
this.initializeInjectorData();
|
||||||
}
|
}
|
||||||
|
|
||||||
toggleSection(event: Event) {
|
toggleSection(event: Event) {
|
||||||
@@ -27,6 +33,28 @@ export class MenuSectionComponent {
|
|||||||
this.menuService.toggleActiveSection(this.menuID, this.section.id);
|
this.menuService.toggleActiveSection(this.menuID, this.section.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
activateSection(event: Event) {
|
||||||
|
event.preventDefault();
|
||||||
|
this.menuService.activateSection(this.menuID, this.section.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
deactivateSection(event: Event) {
|
||||||
|
event.preventDefault();
|
||||||
|
this.menuService.deactivateSection(this.menuID, this.section.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
initializeInjectorData() {
|
||||||
|
this.itemInjectors.set(this.section.id, this.getItemModelInjector(this.section.model));
|
||||||
|
this.itemComponents.set(this.section.id, this.getMenuItemComponent(this.section.model));
|
||||||
|
this.subSections = this.menuService.getSubSectionsByParentID(this.menuID, this.section.id);
|
||||||
|
this.subSections.subscribe((sections: MenuSection[]) => {
|
||||||
|
sections.forEach((section: MenuSection) => {
|
||||||
|
this.itemInjectors.set(section.id, this.getItemModelInjector(section.model));
|
||||||
|
this.itemComponents.set(section.id, this.getMenuItemComponent(section.model));
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
getMenuItemComponent(itemModel?: SectionTypeModel) {
|
getMenuItemComponent(itemModel?: SectionTypeModel) {
|
||||||
if (hasNoValue(itemModel)) {
|
if (hasNoValue(itemModel)) {
|
||||||
itemModel = this.section.model;
|
itemModel = this.section.model;
|
||||||
|
@@ -1,9 +1,9 @@
|
|||||||
import { Component, Injector, OnInit } from '@angular/core';
|
import { ChangeDetectionStrategy, Component, Injector, OnInit } from '@angular/core';
|
||||||
import { Observable } from 'rxjs/internal/Observable';
|
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 { map, tap } from 'rxjs/operators';
|
import { first, map } from 'rxjs/operators';
|
||||||
import { getComponentForMenu } from './menu.decorator';
|
import { getComponentForMenu } from './menu.decorator';
|
||||||
import { GenericConstructor } from '../../core/shared/generic-constructor';
|
import { GenericConstructor } from '../../core/shared/generic-constructor';
|
||||||
import { MenuSectionComponent } from './menu-section/menu-section.component';
|
import { MenuSectionComponent } from './menu-section/menu-section.component';
|
||||||
@@ -15,14 +15,25 @@ import { MenuSectionComponent } from './menu-section/menu-section.component';
|
|||||||
export class MenuComponent implements OnInit {
|
export class MenuComponent implements OnInit {
|
||||||
menuID: MenuID;
|
menuID: MenuID;
|
||||||
menuCollapsed: Observable<boolean>;
|
menuCollapsed: Observable<boolean>;
|
||||||
|
menuVisible: Observable<boolean>;
|
||||||
sections: Observable<MenuSection[]>;
|
sections: Observable<MenuSection[]>;
|
||||||
|
sectionInjectors: Map<string, Injector> = new Map<string, Injector>();
|
||||||
|
sectionComponents: Map<string, GenericConstructor<MenuSectionComponent>> = new Map<string, GenericConstructor<MenuSectionComponent>>();
|
||||||
|
changeDetection: ChangeDetectionStrategy.OnPush;
|
||||||
|
|
||||||
constructor(protected menuService: MenuService, protected injector: Injector) {
|
constructor(protected menuService: MenuService, protected injector: Injector) {
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
this.menuCollapsed = this.menuService.isMenuCollapsed(this.menuID);
|
this.menuCollapsed = this.menuService.isMenuCollapsed(this.menuID);
|
||||||
this.sections = this.menuService.getMenuTopSections(this.menuID);
|
this.menuVisible = this.menuService.isMenuVisible(this.menuID);
|
||||||
|
this.sections = this.menuService.getMenuTopSections(this.menuID).pipe(first());
|
||||||
|
this.sections.subscribe((sections: MenuSection[]) => {
|
||||||
|
sections.forEach((section: MenuSection) => {
|
||||||
|
this.sectionInjectors.set(section.id, this.getSectionDataInjector(section));
|
||||||
|
this.getSectionComponent(section).pipe(first()).subscribe((constr) => this.sectionComponents.set(section.id, constr));
|
||||||
|
})
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
toggle(event: Event) {
|
toggle(event: Event) {
|
||||||
|
54
src/app/shared/menu/menu.module.ts
Normal file
54
src/app/shared/menu/menu.module.ts
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
import { MenuSectionComponent } from './menu-section/menu-section.component';
|
||||||
|
import { MenuComponent } from './menu.component';
|
||||||
|
import { LinkTypeMenuItemComponent } from './type-components/link-type.component';
|
||||||
|
import { TextTypeMenuItemComponent } from './type-components/text-type.component';
|
||||||
|
import { NgModule } from '@angular/core';
|
||||||
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
|
import { RouterModule } from '@angular/router';
|
||||||
|
|
||||||
|
const COMPONENTS = [
|
||||||
|
MenuSectionComponent,
|
||||||
|
MenuComponent,
|
||||||
|
LinkTypeMenuItemComponent,
|
||||||
|
TextTypeMenuItemComponent
|
||||||
|
];
|
||||||
|
|
||||||
|
const ENTRY_COMPONENTS = [
|
||||||
|
LinkTypeMenuItemComponent,
|
||||||
|
TextTypeMenuItemComponent
|
||||||
|
];
|
||||||
|
|
||||||
|
const MODULES = [
|
||||||
|
TranslateModule,
|
||||||
|
RouterModule
|
||||||
|
];
|
||||||
|
const PROVIDERS = [
|
||||||
|
|
||||||
|
];
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
imports: [
|
||||||
|
...MODULES
|
||||||
|
],
|
||||||
|
declarations: [
|
||||||
|
...COMPONENTS,
|
||||||
|
...ENTRY_COMPONENTS,
|
||||||
|
],
|
||||||
|
providers: [
|
||||||
|
...PROVIDERS
|
||||||
|
],
|
||||||
|
exports: [
|
||||||
|
...COMPONENTS,
|
||||||
|
...MODULES
|
||||||
|
],
|
||||||
|
entryComponents: [
|
||||||
|
...ENTRY_COMPONENTS
|
||||||
|
]
|
||||||
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This module handles all components and pipes that need to be shared among multiple other modules
|
||||||
|
*/
|
||||||
|
export class MenuModule {
|
||||||
|
|
||||||
|
}
|
@@ -94,22 +94,31 @@ export function menusReducer(state: MenusState = initialMenusState, action: Menu
|
|||||||
}
|
}
|
||||||
|
|
||||||
function addSection(state: MenusState, action: AddMenuSectionAction) {
|
function addSection(state: MenusState, action: AddMenuSectionAction) {
|
||||||
const newState = addToIndex(state, action.section, action.menuID);
|
// let newState = addToIndex(state, action.section, action.menuID);
|
||||||
return putSectionState(newState, action, action.section);
|
const newState = putSectionState(state, action, action.section);
|
||||||
|
return reorderSections(newState, action)
|
||||||
}
|
}
|
||||||
|
|
||||||
function addToIndex(state: MenusState, section: MenuSection, menuID: MenuID) {
|
function reorderSections(state: MenusState, action: MenuSectionAction) {
|
||||||
const sectionID = section.id;
|
const menuState: MenuState = state[action.menuID];
|
||||||
const parentID = section.parentID;
|
const newSectionState: MenuSections = {};
|
||||||
if (hasValue(parentID)) {
|
const newSectionIndexState: MenuSectionIndex = {};
|
||||||
const menuState: MenuState = state[menuID];
|
Object.values(menuState.sections).sort((sectionA: MenuSection, sectionB: MenuSection) => {
|
||||||
const index = menuState.sectionToSubsectionIndex;
|
const indexA = sectionA.index || 0;
|
||||||
const parentIndex = hasValue(index[parentID]) ? index[parentID] : [];
|
const indexB = sectionB.index || 0;
|
||||||
const newIndex = Object.assign({}, index, { [parentID]: [...parentIndex, sectionID] });
|
return indexA - indexB;
|
||||||
const newMenuState = Object.assign({}, menuState, { sectionToSubsectionIndex: newIndex });
|
}).forEach((section: MenuSection) => {
|
||||||
return Object.assign({}, state, { [menuID]: newMenuState });
|
newSectionState[section.id] = section;
|
||||||
|
if (hasValue(section.parentID)) {
|
||||||
|
const parentIndex = hasValue(newSectionIndexState[section.parentID]) ? newSectionIndexState[section.parentID] : [];
|
||||||
|
newSectionIndexState[section.parentID] = [...parentIndex, section.id];
|
||||||
}
|
}
|
||||||
return state;
|
});
|
||||||
|
const newMenuState = Object.assign({}, menuState, {
|
||||||
|
sections: newSectionState,
|
||||||
|
sectionToSubsectionIndex: newSectionIndexState
|
||||||
|
});
|
||||||
|
return Object.assign({}, state, { [action.menuID]: newMenuState });
|
||||||
}
|
}
|
||||||
|
|
||||||
function removeSection(state: MenusState, action: RemoveMenuSectionAction) {
|
function removeSection(state: MenusState, action: RemoveMenuSectionAction) {
|
||||||
@@ -167,11 +176,11 @@ function toggleActiveSection(state: MenusState, action: ToggleActiveMenuSectionA
|
|||||||
|
|
||||||
function putSectionState(state: MenusState, action: MenuAction, section: MenuSection): MenusState {
|
function putSectionState(state: MenusState, action: MenuAction, section: MenuSection): MenusState {
|
||||||
const menuState: MenuState = state[action.menuID];
|
const menuState: MenuState = state[action.menuID];
|
||||||
const newTopSections = Object.assign({}, menuState.sections, {
|
const newSections = Object.assign({}, menuState.sections, {
|
||||||
[section.id]: section
|
[section.id]: section
|
||||||
});
|
});
|
||||||
const newMenuState = Object.assign({}, menuState, {
|
const newMenuState = Object.assign({}, menuState, {
|
||||||
sections: newTopSections
|
sections: newSections
|
||||||
});
|
});
|
||||||
return Object.assign({}, state, { [action.menuID]: newMenuState });
|
return Object.assign({}, state, { [action.menuID]: newMenuState });
|
||||||
}
|
}
|
||||||
|
@@ -4,10 +4,11 @@ import { MenuSection, MenuSectionIndex, MenuSections, MenusState, MenuState } fr
|
|||||||
import { AppState, keySelector } from '../../app.reducer';
|
import { AppState, keySelector } from '../../app.reducer';
|
||||||
import { MenuID } from './initial-menus-state';
|
import { MenuID } from './initial-menus-state';
|
||||||
import { Observable } from 'rxjs/internal/Observable';
|
import { Observable } from 'rxjs/internal/Observable';
|
||||||
import { distinctUntilChanged, map, switchMap, tap } from 'rxjs/operators';
|
import { map, switchMap } from 'rxjs/operators';
|
||||||
import {
|
import {
|
||||||
AddMenuSectionAction,
|
ActivateMenuSectionAction,
|
||||||
RemoveMenuSectionAction,
|
AddMenuSectionAction, DeactivateMenuSectionAction, HideMenuAction,
|
||||||
|
RemoveMenuSectionAction, ShowMenuAction,
|
||||||
ToggleActiveMenuSectionAction,
|
ToggleActiveMenuSectionAction,
|
||||||
ToggleMenuAction,
|
ToggleMenuAction,
|
||||||
} from './menu.actions';
|
} from './menu.actions';
|
||||||
@@ -42,23 +43,28 @@ export class MenuService {
|
|||||||
return this.store.pipe(select(menuByIDSelector(id)));
|
return this.store.pipe(select(menuByIDSelector(id)));
|
||||||
}
|
}
|
||||||
|
|
||||||
getMenuTopSections(menuID: MenuID): Observable<MenuSection[]> {
|
getMenuTopSections(menuID: MenuID, mustBeVisible = true): Observable<MenuSection[]> {
|
||||||
return this.store.pipe(
|
return this.store.pipe(
|
||||||
select(menuByIDSelector(menuID)),
|
select(menuByIDSelector(menuID)),
|
||||||
select(menuSectionStateSelector),
|
select(menuSectionStateSelector),
|
||||||
map((sections: MenuSections) => {
|
map((sections: MenuSections) => {
|
||||||
return Object.values(sections).filter((section: MenuSection) => hasNoValue(section.parentID))
|
return Object.values(sections)
|
||||||
|
.filter((section: MenuSection) => hasNoValue(section.parentID))
|
||||||
|
.filter((section: MenuSection) => !mustBeVisible || section.visible)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
getSubSectionsByParentID(menuID: MenuID, parentID: string): Observable<MenuSection[]> {
|
getSubSectionsByParentID(menuID: MenuID, parentID: string, mustBeVisible = true): Observable<MenuSection[]> {
|
||||||
return this.store.pipe(
|
return this.store.pipe(
|
||||||
select(menuByIDSelector(menuID)),
|
select(menuByIDSelector(menuID)),
|
||||||
select(getSubSectionsFromSectionSelector(parentID)),
|
select(getSubSectionsFromSectionSelector(parentID)),
|
||||||
map((ids: string[]) => isNotEmpty(ids) ? ids : []),
|
map((ids: string[]) => isNotEmpty(ids) ? ids : []),
|
||||||
switchMap((ids: string[]) => observableCombineLatest(ids.map((id: string) => this.getMenuSection(menuID, id)))),
|
switchMap((ids: string[]) =>
|
||||||
|
observableCombineLatest(ids.map((id: string) => this.getMenuSection(menuID, id)))
|
||||||
|
),
|
||||||
|
map((sections: MenuSection[]) => sections.filter((section: MenuSection) => !mustBeVisible || section.visible))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -91,20 +97,38 @@ export class MenuService {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
isMenuVisible(menuID: MenuID): Observable<boolean> {
|
||||||
|
return this.getMenu(menuID).pipe(
|
||||||
|
map((state: MenuState) => state.visible)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
toggleMenu(menuID: MenuID): void {
|
toggleMenu(menuID: MenuID): void {
|
||||||
this.store.dispatch(new ToggleMenuAction(menuID));
|
this.store.dispatch(new ToggleMenuAction(menuID));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
showMenu(menuID: MenuID): void {
|
||||||
|
this.store.dispatch(new ShowMenuAction(menuID));
|
||||||
|
}
|
||||||
|
|
||||||
|
hideMenu(menuID: MenuID): void {
|
||||||
|
this.store.dispatch(new HideMenuAction(menuID));
|
||||||
|
}
|
||||||
|
|
||||||
toggleActiveSection(menuID: MenuID, id: string): void {
|
toggleActiveSection(menuID: MenuID, id: string): void {
|
||||||
this.store.dispatch(new ToggleActiveMenuSectionAction(menuID, id));
|
this.store.dispatch(new ToggleActiveMenuSectionAction(menuID, id));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
activateSection(menuID: MenuID, id: string): void {
|
||||||
|
this.store.dispatch(new ActivateMenuSectionAction(menuID, id));
|
||||||
|
}
|
||||||
|
|
||||||
|
deactivateSection(menuID: MenuID, id: string): void {
|
||||||
|
this.store.dispatch(new DeactivateMenuSectionAction(menuID, id));
|
||||||
|
}
|
||||||
|
|
||||||
isSectionActive(menuID: MenuID, id: string): Observable<boolean> {
|
isSectionActive(menuID: MenuID, id: string): Observable<boolean> {
|
||||||
return this.getMenuSection(menuID, id).pipe(tap((section) => console.log(section.id)), map((section) => section.active),
|
return this.getMenuSection(menuID, id).pipe(map((section) => section.active));
|
||||||
distinctUntilChanged((a, b) => {
|
|
||||||
console.log('DISTINCT', a, b);
|
|
||||||
return a === b
|
|
||||||
}));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
isSectionVisible(menuID: MenuID, id: string): Observable<boolean> {
|
isSectionVisible(menuID: MenuID, id: string): Observable<boolean> {
|
||||||
|
@@ -1 +1 @@
|
|||||||
<a class="nav-item nav-link" [href]="item.link">{{item.text | translate}}</a>
|
<a class="nav-item nav-link" [routerLink]="item.link">{{item.text | translate}}</a>
|
@@ -85,10 +85,7 @@ import { InputSuggestionsComponent } from './input-suggestions/input-suggestions
|
|||||||
import { CapitalizePipe } from './utils/capitalize.pipe';
|
import { CapitalizePipe } from './utils/capitalize.pipe';
|
||||||
import { ObjectKeysPipe } from './utils/object-keys-pipe';
|
import { ObjectKeysPipe } from './utils/object-keys-pipe';
|
||||||
import { MomentModule } from 'ngx-moment';
|
import { MomentModule } from 'ngx-moment';
|
||||||
import { MenuSectionComponent } from './menu/menu-section/menu-section.component';
|
import { MenuModule } from './menu/menu.module';
|
||||||
import { MenuComponent } from './menu/menu.component';
|
|
||||||
import { LinkTypeMenuItemComponent } from './menu/type-components/link-type.component';
|
|
||||||
import { TextTypeMenuItemComponent } from './menu/type-components/text-type.component';
|
|
||||||
|
|
||||||
const MODULES = [
|
const MODULES = [
|
||||||
// Do NOT include UniversalModule, HttpModule, or JsonpModule here
|
// Do NOT include UniversalModule, HttpModule, or JsonpModule here
|
||||||
@@ -109,7 +106,8 @@ const MODULES = [
|
|||||||
TranslateModule,
|
TranslateModule,
|
||||||
NouisliderModule,
|
NouisliderModule,
|
||||||
MomentModule,
|
MomentModule,
|
||||||
TextMaskModule
|
TextMaskModule,
|
||||||
|
MenuModule
|
||||||
];
|
];
|
||||||
|
|
||||||
const PIPES = [
|
const PIPES = [
|
||||||
@@ -163,10 +161,6 @@ const COMPONENTS = [
|
|||||||
TruncatablePartComponent,
|
TruncatablePartComponent,
|
||||||
BrowseByComponent,
|
BrowseByComponent,
|
||||||
InputSuggestionsComponent,
|
InputSuggestionsComponent,
|
||||||
MenuSectionComponent,
|
|
||||||
MenuComponent,
|
|
||||||
LinkTypeMenuItemComponent,
|
|
||||||
TextTypeMenuItemComponent
|
|
||||||
];
|
];
|
||||||
|
|
||||||
const ENTRY_COMPONENTS = [
|
const ENTRY_COMPONENTS = [
|
||||||
@@ -179,9 +173,7 @@ const ENTRY_COMPONENTS = [
|
|||||||
CollectionGridElementComponent,
|
CollectionGridElementComponent,
|
||||||
CommunityGridElementComponent,
|
CommunityGridElementComponent,
|
||||||
SearchResultGridElementComponent,
|
SearchResultGridElementComponent,
|
||||||
BrowseEntryListElementComponent,
|
BrowseEntryListElementComponent
|
||||||
LinkTypeMenuItemComponent,
|
|
||||||
TextTypeMenuItemComponent
|
|
||||||
];
|
];
|
||||||
|
|
||||||
const PROVIDERS = [
|
const PROVIDERS = [
|
||||||
|
Reference in New Issue
Block a user