mirror of
https://github.com/SuiteCRM/SuiteCRM-Core.git
synced 2025-08-29 04:21:06 +08:00
Display selected module on navbar
- Add current module and action to AppState - Calculate current module and action in BaseModuleResolver -- Add logic to show current module as active on --- import and find duplicates - Update legacy handler method to retrieve available modules -- Also send invisible modules, to --- Allow to display invisible modules as the selected module -- Update module name mape - Retrieve number of navbar items from backed - Fix grouped tab links - Simplify navbar abstract by simplifying parameters -- passing language map instead of individual language -- passing navigation object instead of individual parts - Create smaller components for repeated parts of navbar -- grouped-menu-item -- menu-item -- menu-item-link -- menu-item-list -- home-menu-item -- home-menu-recently-viewed - Removing "jsdoc/no-types" from eslint -- To avoid conflicts with jsdoc/require-param-type
This commit is contained in:
parent
3672c445e7
commit
f10cc2812f
47 changed files with 1860 additions and 368 deletions
|
@ -111,7 +111,6 @@ module.exports = {
|
|||
"id-match": "error",
|
||||
"import/no-deprecated": "warn",
|
||||
"import/order": "off",
|
||||
"jsdoc/no-types": "error",
|
||||
"max-classes-per-file": "off",
|
||||
"max-len": [
|
||||
"error",
|
||||
|
|
|
@ -29,6 +29,7 @@ services:
|
|||
$exposedUserPreferences: '%legacy.exposed_user_preferences%'
|
||||
$themeImagePaths: '%themes.image_paths%'
|
||||
$themeImageSupportedTypes: '%themes.image_supported_types%'
|
||||
$frontendExcludedModules: '%legacy.frontend_excluded_modules%'
|
||||
_instanceof:
|
||||
App\Service\ProcessHandlerInterface:
|
||||
tags: ['app.process.handler']
|
||||
|
|
6
config/services/legacy/frontend_excluded_modules.yaml
Normal file
6
config/services/legacy/frontend_excluded_modules.yaml
Normal file
|
@ -0,0 +1,6 @@
|
|||
parameters:
|
||||
legacy.frontend_excluded_modules:
|
||||
- EmailText
|
||||
- TeamMemberships
|
||||
- TeamSets
|
||||
- TeamSetModule
|
|
@ -1,6 +1,6 @@
|
|||
import {ActionLinkModel} from './action-link-model';
|
||||
import {MenuItem} from '@components/navbar/navbar.abstract';
|
||||
|
||||
export interface AllMenuModel {
|
||||
modules: Array<ActionLinkModel>;
|
||||
extra: Array<ActionLinkModel>;
|
||||
modules: MenuItem[];
|
||||
extra: MenuItem[];
|
||||
}
|
||||
|
|
|
@ -0,0 +1,41 @@
|
|||
<ng-container>
|
||||
<span data-toggle="collapse" data-target=".navbar-collapse">
|
||||
<a class="top-nav-link nav-link-grouped dropdown-toggle" data-toggle="dropdown">
|
||||
{{ item.link.label }}
|
||||
</a>
|
||||
</span>
|
||||
|
||||
<ul class="dropdown-menu main"
|
||||
aria-labelledby="navbarDropdownMenuLink"
|
||||
[ngbCollapse]="subNavCollapse"
|
||||
>
|
||||
<li class="nav-item dropdown-submenu submenu" *ngFor="let sub of item.submenu">
|
||||
|
||||
<scrm-menu-item-link [link]="sub.link"
|
||||
[class]="{
|
||||
'sub-nav-link': true,
|
||||
'nav-link': true,
|
||||
'action-link': true,
|
||||
'dropdown-item': sub.submenu.length,
|
||||
'dropdown-toggle': sub.submenu.length
|
||||
}"
|
||||
>
|
||||
</scrm-menu-item-link>
|
||||
|
||||
<ul *ngIf="sub.submenu.length" class="dropdown-menu submenu">
|
||||
|
||||
<li *ngFor="let subitem of sub.submenu" class="nav-item">
|
||||
|
||||
<scrm-menu-item-link [class]="'submenu-nav-link nav-link action-link'"
|
||||
[link]="subitem.link"
|
||||
[icon]="subitem.icon">
|
||||
</scrm-menu-item-link>
|
||||
</li>
|
||||
<li>
|
||||
<scrm-menu-recently-viewed [languages]="languages"
|
||||
[records]="sub.recentRecords"></scrm-menu-recently-viewed>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</ng-container>
|
|
@ -0,0 +1,168 @@
|
|||
import {async, ComponentFixture, TestBed} from '@angular/core/testing';
|
||||
|
||||
import {GroupedMenuItemComponent} from './grouped-menu-item.component';
|
||||
import {MenuItemLinkComponent} from '@components/navbar/menu-item-link/menu-item-link.component';
|
||||
import {MenuRecentlyViewedComponent} from '@components/navbar/menu-recently-viewed/menu-recently-viewed.component';
|
||||
import {AngularSvgIconModule} from 'angular-svg-icon';
|
||||
import {RouterTestingModule} from '@angular/router/testing';
|
||||
import {HttpClientTestingModule} from '@angular/common/http/testing';
|
||||
import {ImageModule} from '@components/image/image.module';
|
||||
import {NgbModule} from '@ng-bootstrap/ng-bootstrap';
|
||||
import {ThemeImagesFacade} from '@base/facades/theme-images/theme-images.facade';
|
||||
import {of} from 'rxjs';
|
||||
import {themeImagesMockData} from '@base/facades/theme-images/theme-images.facade.spec.mock';
|
||||
import {take} from 'rxjs/operators';
|
||||
import {Component} from '@angular/core';
|
||||
import {MenuItem} from '@components/navbar/navbar.abstract';
|
||||
import {LanguageStrings} from '@base/facades/language/language.facade';
|
||||
import {languageMockData} from '@base/facades/language/language.facade.spec.mock';
|
||||
|
||||
const groupedMockMenuItem = {
|
||||
link: {
|
||||
url: '',
|
||||
label: 'Top Link Label',
|
||||
route: '',
|
||||
},
|
||||
icon: '',
|
||||
submenu: [
|
||||
{
|
||||
link: {
|
||||
url: '',
|
||||
label: 'Sub Link 1',
|
||||
route: '/fake-module',
|
||||
},
|
||||
icon: '',
|
||||
submenu: [
|
||||
{
|
||||
link: {
|
||||
url: '',
|
||||
label: 'Sub Link 1 item 1',
|
||||
route: '/fake-module/edit',
|
||||
},
|
||||
icon: 'plus',
|
||||
submenu: []
|
||||
},
|
||||
]
|
||||
},
|
||||
{
|
||||
link: {
|
||||
url: '',
|
||||
label: 'Sub Link 2',
|
||||
route: '/fake-module/list',
|
||||
},
|
||||
icon: '',
|
||||
submenu: [
|
||||
{
|
||||
link: {
|
||||
url: '',
|
||||
label: 'Sub Link 2 item 1',
|
||||
route: '/fake-module/edit',
|
||||
},
|
||||
icon: 'plus',
|
||||
submenu: []
|
||||
},
|
||||
]
|
||||
}
|
||||
],
|
||||
recentRecords: null
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'grouped-menu-item-test-host-component',
|
||||
template: '<scrm-grouped-menu-item [item]="item" [languages]="languages" [subNavCollapse]="subNavCollapse"></scrm-grouped-menu-item>'
|
||||
})
|
||||
class GroupedMenuItemTestHostComponent {
|
||||
item: MenuItem = groupedMockMenuItem;
|
||||
languages: LanguageStrings = {
|
||||
...languageMockData,
|
||||
languageKey: 'en_us'
|
||||
};
|
||||
subNavCollapse = true;
|
||||
|
||||
setItem(value: MenuItem): void {
|
||||
this.item = value;
|
||||
}
|
||||
|
||||
setLanguages(value: LanguageStrings): void {
|
||||
this.languages = value;
|
||||
}
|
||||
}
|
||||
|
||||
describe('GroupedMenuItemComponent', () => {
|
||||
let testHostComponent: GroupedMenuItemTestHostComponent;
|
||||
let testHostFixture: ComponentFixture<GroupedMenuItemTestHostComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [
|
||||
GroupedMenuItemComponent,
|
||||
MenuItemLinkComponent,
|
||||
GroupedMenuItemTestHostComponent,
|
||||
MenuRecentlyViewedComponent
|
||||
],
|
||||
imports: [
|
||||
AngularSvgIconModule,
|
||||
RouterTestingModule,
|
||||
HttpClientTestingModule,
|
||||
ImageModule,
|
||||
NgbModule
|
||||
],
|
||||
providers: [
|
||||
{
|
||||
provide: ThemeImagesFacade, useValue: {
|
||||
images$: of(themeImagesMockData).pipe(take(1))
|
||||
}
|
||||
},
|
||||
],
|
||||
}).compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
testHostFixture = TestBed.createComponent(GroupedMenuItemTestHostComponent);
|
||||
testHostComponent = testHostFixture.componentInstance;
|
||||
testHostFixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(testHostComponent).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should have top link', () => {
|
||||
|
||||
|
||||
const spanElement = testHostFixture.nativeElement.querySelector('span');
|
||||
const topLinks = spanElement.getElementsByClassName('top-nav-link');
|
||||
const topLink = topLinks[0];
|
||||
|
||||
expect(testHostComponent).toBeTruthy();
|
||||
expect(spanElement).toBeTruthy();
|
||||
expect(topLinks.length).toEqual(1);
|
||||
expect(topLink.textContent).toContain(groupedMockMenuItem.link.label);
|
||||
|
||||
});
|
||||
|
||||
it('should have sub links', () => {
|
||||
|
||||
const ulElement = testHostFixture.nativeElement.querySelector('ul');
|
||||
const subLinks = ulElement.getElementsByClassName('sub-nav-link');
|
||||
|
||||
expect(testHostComponent).toBeTruthy();
|
||||
expect(ulElement).toBeTruthy();
|
||||
expect(subLinks.length).toEqual(2);
|
||||
});
|
||||
|
||||
it('should have sub links items', () => {
|
||||
|
||||
const ulElement = testHostFixture.nativeElement.querySelector('ul');
|
||||
const subLinks = ulElement.getElementsByClassName('submenu');
|
||||
|
||||
let subLink; let subLinksItems;
|
||||
for (let i = 0; i < 2; i++) {
|
||||
subLink = subLinks[i];
|
||||
subLinksItems = subLink.getElementsByClassName('submenu-nav-link');
|
||||
|
||||
expect(subLinksItems.length).toEqual(1);
|
||||
expect(subLinksItems[0].textContent).toContain(`item 1`);
|
||||
}
|
||||
});
|
||||
});
|
|
@ -0,0 +1,17 @@
|
|||
import {Component, Input} from '@angular/core';
|
||||
import {MenuItem} from '@components/navbar/navbar.abstract';
|
||||
import {LanguageStrings} from '@base/facades/language/language.facade';
|
||||
|
||||
@Component({
|
||||
selector: 'scrm-grouped-menu-item',
|
||||
templateUrl: './grouped-menu-item.component.html',
|
||||
styleUrls: []
|
||||
})
|
||||
export class GroupedMenuItemComponent {
|
||||
@Input() item: MenuItem;
|
||||
@Input() languages: LanguageStrings;
|
||||
@Input() subNavCollapse: boolean;
|
||||
|
||||
constructor() {
|
||||
}
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
<ul class="navbar-nav home-nav">
|
||||
<li class="nav-item" [class.active]="active">
|
||||
<a class="home-nav-link" [routerLink]="route">
|
||||
<scrm-image class="home-icon" image="home"></scrm-image>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
|
@ -0,0 +1,100 @@
|
|||
import {async, ComponentFixture, TestBed} from '@angular/core/testing';
|
||||
|
||||
import {HomeMenuItemComponent} from './home-menu-item.component';
|
||||
import {MenuItemLinkComponent} from '@components/navbar/menu-item-link/menu-item-link.component';
|
||||
import {AngularSvgIconModule} from 'angular-svg-icon';
|
||||
import {RouterTestingModule} from '@angular/router/testing';
|
||||
import {HttpClientTestingModule} from '@angular/common/http/testing';
|
||||
import {ImageModule} from '@components/image/image.module';
|
||||
import {NgbModule} from '@ng-bootstrap/ng-bootstrap';
|
||||
import {ThemeImagesFacade} from '@base/facades/theme-images/theme-images.facade';
|
||||
import {of} from 'rxjs';
|
||||
import {themeImagesMockData} from '@base/facades/theme-images/theme-images.facade.spec.mock';
|
||||
import {take} from 'rxjs/operators';
|
||||
import {Component} from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'home-menu-item-test-host-component',
|
||||
template: '<scrm-home-menu-item [active]="active" [route]="route"></scrm-home-menu-item>'
|
||||
})
|
||||
class HomeMenuItemTestHostComponent {
|
||||
active = false;
|
||||
route = '';
|
||||
|
||||
setActive(value: boolean): void {
|
||||
this.active = value;
|
||||
}
|
||||
|
||||
setRoute(value: string): void {
|
||||
this.route = value;
|
||||
}
|
||||
}
|
||||
|
||||
describe('HomeMenuItemComponent', () => {
|
||||
let testHostComponent: HomeMenuItemTestHostComponent;
|
||||
let testHostFixture: ComponentFixture<HomeMenuItemTestHostComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [HomeMenuItemComponent, MenuItemLinkComponent, HomeMenuItemTestHostComponent],
|
||||
imports: [
|
||||
AngularSvgIconModule,
|
||||
RouterTestingModule,
|
||||
HttpClientTestingModule,
|
||||
ImageModule,
|
||||
NgbModule,
|
||||
],
|
||||
providers: [
|
||||
{
|
||||
provide: ThemeImagesFacade, useValue: {
|
||||
images$: of(themeImagesMockData).pipe(take(1))
|
||||
}
|
||||
},
|
||||
],
|
||||
}).compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
testHostFixture = TestBed.createComponent(HomeMenuItemTestHostComponent);
|
||||
testHostComponent = testHostFixture.componentInstance;
|
||||
testHostFixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(testHostComponent).toBeTruthy();
|
||||
const linkElement = testHostFixture.nativeElement.querySelector('li');
|
||||
|
||||
expect(linkElement).toBeTruthy();
|
||||
expect(linkElement.className).not.toContain('active');
|
||||
});
|
||||
|
||||
it('should have image', () => {
|
||||
expect(testHostComponent).toBeTruthy();
|
||||
|
||||
expect(testHostFixture.nativeElement.querySelector('svg-icon')).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should have image', () => {
|
||||
expect(testHostComponent).toBeTruthy();
|
||||
|
||||
testHostComponent.setActive(true)
|
||||
testHostFixture.detectChanges();
|
||||
const linkElement = testHostFixture.nativeElement.querySelector('li');
|
||||
|
||||
expect(linkElement).toBeTruthy();
|
||||
expect(linkElement.className).toContain('active');
|
||||
});
|
||||
|
||||
it('should have image', () => {
|
||||
expect(testHostComponent).toBeTruthy();
|
||||
|
||||
testHostComponent.setRoute('/home')
|
||||
testHostFixture.detectChanges();
|
||||
const linkElement = testHostFixture.nativeElement.querySelector('a');
|
||||
const url = '/home';
|
||||
|
||||
expect(linkElement).toBeTruthy();
|
||||
expect(linkElement.href).toContain(url);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,15 @@
|
|||
import {Component, Input} from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'scrm-home-menu-item',
|
||||
templateUrl: './home-menu-item.component.html',
|
||||
styleUrls: []
|
||||
})
|
||||
export class HomeMenuItemComponent {
|
||||
@Input() route: string;
|
||||
@Input() active: boolean;
|
||||
|
||||
constructor() {
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
<a *ngIf="link.route"
|
||||
[ngClass]="class"
|
||||
[routerLink]="link.route"
|
||||
[queryParams]="link.params"
|
||||
>
|
||||
<scrm-image *ngIf="icon" image="{{ icon }}"></scrm-image>
|
||||
{{ link.label }}
|
||||
</a>
|
||||
<a *ngIf="!link.route"
|
||||
[ngClass]="class"
|
||||
[href]="link.url"
|
||||
>
|
||||
<scrm-image *ngIf="icon" image="{{ icon }}"></scrm-image>
|
||||
{{ link.label }}
|
||||
</a>
|
||||
|
|
@ -0,0 +1,146 @@
|
|||
import {Component} from '@angular/core';
|
||||
import {async, ComponentFixture, TestBed} from '@angular/core/testing';
|
||||
import {HttpClientTestingModule} from '@angular/common/http/testing';
|
||||
import {RouterTestingModule} from '@angular/router/testing';
|
||||
import {of} from 'rxjs';
|
||||
import {take} from 'rxjs/operators';
|
||||
import {NgbModule} from '@ng-bootstrap/ng-bootstrap';
|
||||
import {AngularSvgIconModule} from 'angular-svg-icon';
|
||||
|
||||
|
||||
import {themeImagesMockData} from '@base/facades/theme-images/theme-images.facade.spec.mock';
|
||||
|
||||
import {MenuItemLinkComponent} from './menu-item-link.component';
|
||||
import {MenuItemLink} from '@components/navbar/navbar.abstract';
|
||||
import {ThemeImagesFacade} from '@base/facades/theme-images/theme-images.facade';
|
||||
import {ImageModule} from '@components/image/image.module';
|
||||
|
||||
const mockLink = {
|
||||
label: 'Test Link Label',
|
||||
url: '/fake-module/edit?return_module=Opportunities&return_action=DetailView',
|
||||
route: '/fake-module/edit',
|
||||
params: {
|
||||
'return-module': 'FakeModule',
|
||||
'return-action': 'DetailView',
|
||||
}
|
||||
};
|
||||
|
||||
@Component({
|
||||
selector: 'menu-item-link-test-host-component',
|
||||
template: '<scrm-menu-item-link [class]="class" [link]="link" [icon]="icon"></scrm-menu-item-link>'
|
||||
})
|
||||
class MenuItemLinkTestHostComponent {
|
||||
class = '';
|
||||
link: MenuItemLink = mockLink;
|
||||
icon = '';
|
||||
|
||||
setClass(value: string): void {
|
||||
this.class = value;
|
||||
}
|
||||
|
||||
setLink(value: MenuItemLink): void {
|
||||
this.link = value;
|
||||
}
|
||||
|
||||
setIcon(value: string): void {
|
||||
this.icon = value;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
describe('MenuItemActionLinkComponent', () => {
|
||||
let testHostComponent: MenuItemLinkTestHostComponent;
|
||||
let testHostFixture: ComponentFixture<MenuItemLinkTestHostComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [MenuItemLinkComponent, MenuItemLinkTestHostComponent],
|
||||
imports: [
|
||||
AngularSvgIconModule,
|
||||
RouterTestingModule,
|
||||
HttpClientTestingModule,
|
||||
ImageModule,
|
||||
NgbModule,
|
||||
],
|
||||
providers: [
|
||||
{
|
||||
provide: ThemeImagesFacade, useValue: {
|
||||
images$: of(themeImagesMockData).pipe(take(1))
|
||||
}
|
||||
},
|
||||
],
|
||||
}).compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
testHostFixture = TestBed.createComponent(MenuItemLinkTestHostComponent);
|
||||
testHostComponent = testHostFixture.componentInstance;
|
||||
testHostFixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(testHostComponent).toBeTruthy();
|
||||
const linkElement = testHostFixture.nativeElement.querySelector('a');
|
||||
|
||||
expect(linkElement).toBeTruthy();
|
||||
expect(linkElement.className).toEqual('');
|
||||
expect(testHostFixture.nativeElement.querySelector('svg-icon')).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should have image', () => {
|
||||
expect(testHostComponent).toBeTruthy();
|
||||
|
||||
testHostComponent.setIcon('plus')
|
||||
testHostFixture.detectChanges();
|
||||
|
||||
expect(testHostFixture.nativeElement.querySelector('svg-icon')).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should use route', () => {
|
||||
|
||||
expect(testHostComponent).toBeTruthy();
|
||||
|
||||
const linkElement = testHostFixture.nativeElement.querySelector('a');
|
||||
const url = '/fake-module/edit?return-module=FakeModule&return-action=DetailView';
|
||||
|
||||
expect(linkElement).toBeTruthy();
|
||||
expect(linkElement.href).toContain(url);
|
||||
expect(linkElement.text).toContain(mockLink.label);
|
||||
});
|
||||
|
||||
it('should use href', () => {
|
||||
expect(testHostComponent).toBeTruthy();
|
||||
|
||||
const link = {
|
||||
label: 'Test Link Label',
|
||||
url: './fake-module/edit?return_module=FakeModule&return_action=DetailView',
|
||||
route: null,
|
||||
params: null
|
||||
};
|
||||
|
||||
testHostComponent.setLink(link)
|
||||
testHostFixture.detectChanges();
|
||||
|
||||
|
||||
const linkElement = testHostFixture.nativeElement.querySelector('a');
|
||||
const url = '/fake-module/edit?return_module=FakeModule&return_action=DetailView';
|
||||
expect(linkElement).toBeTruthy();
|
||||
expect(linkElement.href).toContain(url);
|
||||
expect(linkElement.text).toContain(mockLink.label);
|
||||
});
|
||||
|
||||
it('should have class', () => {
|
||||
expect(testHostComponent).toBeTruthy();
|
||||
|
||||
const testClass = 'test-class';
|
||||
testHostComponent.setClass(testClass)
|
||||
testHostFixture.detectChanges();
|
||||
|
||||
|
||||
const linkElement = testHostFixture.nativeElement.querySelector('a');
|
||||
|
||||
expect(linkElement).toBeTruthy();
|
||||
expect(linkElement.className).toContain(testClass);
|
||||
});
|
||||
|
||||
});
|
|
@ -0,0 +1,15 @@
|
|||
import {Component, Input} from '@angular/core';
|
||||
import {MenuItemLink} from '@components/navbar/navbar.abstract';
|
||||
|
||||
@Component({
|
||||
selector: 'scrm-menu-item-link',
|
||||
templateUrl: './menu-item-link.component.html',
|
||||
styleUrls: []
|
||||
})
|
||||
export class MenuItemLinkComponent {
|
||||
@Input() link: MenuItemLink;
|
||||
@Input() icon: string;
|
||||
@Input() class: string;
|
||||
constructor() {
|
||||
}
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
<ng-container>
|
||||
<span data-toggle="collapse" data-target=".navbar-collapse">
|
||||
|
||||
<scrm-menu-item-link
|
||||
[class]="{
|
||||
'top-nav-link': true,
|
||||
'nav-link-nongrouped': true,
|
||||
'dropdown-toggle': item.submenu.length
|
||||
}"
|
||||
[link]="item.link">
|
||||
</scrm-menu-item-link>
|
||||
|
||||
</span>
|
||||
<div *ngIf="item.submenu.length"
|
||||
aria-labelledby="navbarDropdownMenuLink"
|
||||
class="dropdown-menu submenu">
|
||||
|
||||
<div class="nav-item" *ngFor="let sub of item.submenu">
|
||||
|
||||
<scrm-menu-item-link
|
||||
[class]="'sub-nav-link nav-link action-link'"
|
||||
[link]="sub.link"
|
||||
[icon]="sub.icon">
|
||||
</scrm-menu-item-link>
|
||||
|
||||
</div>
|
||||
|
||||
<scrm-menu-recently-viewed [languages]="languages" [records]="item.recentRecords"></scrm-menu-recently-viewed>
|
||||
</div>
|
||||
</ng-container>
|
|
@ -0,0 +1,132 @@
|
|||
import {async, ComponentFixture, TestBed} from '@angular/core/testing';
|
||||
|
||||
import {MenuItemComponent} from './menu-item.component';
|
||||
import {Component} from '@angular/core';
|
||||
import {MenuItemLinkComponent} from '@components/navbar/menu-item-link/menu-item-link.component';
|
||||
import {AngularSvgIconModule} from 'angular-svg-icon';
|
||||
import {RouterTestingModule} from '@angular/router/testing';
|
||||
import {HttpClientTestingModule} from '@angular/common/http/testing';
|
||||
import {ImageModule} from '@components/image/image.module';
|
||||
import {NgbModule} from '@ng-bootstrap/ng-bootstrap';
|
||||
import {ThemeImagesFacade} from '@base/facades/theme-images/theme-images.facade';
|
||||
import {of} from 'rxjs';
|
||||
import {themeImagesMockData} from '@base/facades/theme-images/theme-images.facade.spec.mock';
|
||||
import {take} from 'rxjs/operators';
|
||||
import {MenuItem} from '@components/navbar/navbar.abstract';
|
||||
import {LanguageStrings} from '@base/facades/language/language.facade';
|
||||
import {languageMockData} from '@base/facades/language/language.facade.spec.mock';
|
||||
import {MenuRecentlyViewedComponent} from '@components/navbar/menu-recently-viewed/menu-recently-viewed.component';
|
||||
|
||||
const mockMenuItem = {
|
||||
link: {
|
||||
url: '',
|
||||
label: 'Top Link Label',
|
||||
route: '/fake-module',
|
||||
},
|
||||
icon: '',
|
||||
submenu: [
|
||||
{
|
||||
link: {
|
||||
url: '',
|
||||
label: 'Sub Link 1',
|
||||
route: '/fake-module/edit',
|
||||
},
|
||||
icon: 'plus',
|
||||
submenu: []
|
||||
},
|
||||
{
|
||||
link: {
|
||||
url: '',
|
||||
label: 'Sub Link 2',
|
||||
route: '/fake-module/list',
|
||||
},
|
||||
icon: 'view',
|
||||
submenu: []
|
||||
}
|
||||
],
|
||||
recentRecords: null
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'menu-item-test-host-component',
|
||||
template: '<scrm-menu-item [item]="item" [languages]="languages"></scrm-menu-item>'
|
||||
})
|
||||
class MenuItemTestHostComponent {
|
||||
item: MenuItem = mockMenuItem;
|
||||
languages: LanguageStrings = {
|
||||
...languageMockData,
|
||||
languageKey: 'en_us'
|
||||
};
|
||||
|
||||
setItem(value: MenuItem): void {
|
||||
this.item = value;
|
||||
}
|
||||
|
||||
setLanguages(value: LanguageStrings): void {
|
||||
this.languages = value;
|
||||
}
|
||||
}
|
||||
|
||||
describe('ModuleMenuItemComponent', () => {
|
||||
let testHostComponent: MenuItemTestHostComponent;
|
||||
let testHostFixture: ComponentFixture<MenuItemTestHostComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [
|
||||
MenuItemComponent,
|
||||
MenuItemLinkComponent,
|
||||
MenuItemTestHostComponent,
|
||||
MenuRecentlyViewedComponent
|
||||
],
|
||||
imports: [
|
||||
AngularSvgIconModule,
|
||||
RouterTestingModule,
|
||||
HttpClientTestingModule,
|
||||
ImageModule,
|
||||
NgbModule
|
||||
],
|
||||
providers: [
|
||||
{
|
||||
provide: ThemeImagesFacade, useValue: {
|
||||
images$: of(themeImagesMockData).pipe(take(1))
|
||||
}
|
||||
},
|
||||
],
|
||||
}).compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
testHostFixture = TestBed.createComponent(MenuItemTestHostComponent);
|
||||
testHostComponent = testHostFixture.componentInstance;
|
||||
testHostFixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(testHostComponent).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should have top link', () => {
|
||||
|
||||
|
||||
const spanElement = testHostFixture.nativeElement.querySelector('span');
|
||||
const topLinks = spanElement.getElementsByClassName('top-nav-link');
|
||||
const topLink = topLinks[0];
|
||||
|
||||
expect(testHostComponent).toBeTruthy();
|
||||
expect(spanElement).toBeTruthy();
|
||||
expect(topLinks.length).toEqual(1);
|
||||
expect(topLink.textContent).toContain(mockMenuItem.link.label);
|
||||
|
||||
});
|
||||
|
||||
it('should have sub links', () => {
|
||||
|
||||
const divElement = testHostFixture.nativeElement.querySelector('div');
|
||||
const subLinks = divElement.getElementsByClassName('sub-nav-link');
|
||||
|
||||
expect(testHostComponent).toBeTruthy();
|
||||
expect(divElement).toBeTruthy();
|
||||
expect(subLinks.length).toEqual(2);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,16 @@
|
|||
import {Component, Input} from '@angular/core';
|
||||
import {MenuItem} from '@components/navbar/navbar.abstract';
|
||||
import {LanguageStrings} from '@base/facades/language/language.facade';
|
||||
|
||||
@Component({
|
||||
selector: 'scrm-menu-item',
|
||||
templateUrl: './menu-item.component.html',
|
||||
styleUrls: []
|
||||
})
|
||||
export class MenuItemComponent {
|
||||
@Input() item: MenuItem;
|
||||
@Input() languages: LanguageStrings;
|
||||
|
||||
constructor() {
|
||||
}
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
<ul *ngIf="items && items.length > 0" class="navbar-nav">
|
||||
|
||||
<li class="top-nav nav-item dropdown non-grouped">
|
||||
|
||||
<a class="nav-link-nongrouped dropdown-toggle">{{label}}</a>
|
||||
|
||||
<div aria-labelledby="navbarDropdownMenuLink"
|
||||
class="dropdown-menu more-menu submenu"
|
||||
>
|
||||
<div class="nav-item" *ngFor="let item of items">
|
||||
<scrm-menu-item-link [class]="'nav-link action-link'" [link]="item.link">
|
||||
</scrm-menu-item-link>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
|
@ -0,0 +1,109 @@
|
|||
import {async, ComponentFixture, TestBed} from '@angular/core/testing';
|
||||
import {of} from 'rxjs';
|
||||
import {take} from 'rxjs/operators';
|
||||
import {Component} from '@angular/core';
|
||||
import {RouterTestingModule} from '@angular/router/testing';
|
||||
import {HttpClientTestingModule} from '@angular/common/http/testing';
|
||||
|
||||
import {AngularSvgIconModule} from 'angular-svg-icon';
|
||||
import {ImageModule} from '@components/image/image.module';
|
||||
import {NgbModule} from '@ng-bootstrap/ng-bootstrap';
|
||||
import {MenuItemsListComponent} from './menu-items-list.component';
|
||||
import {ThemeImagesFacade} from '@store/theme-images/theme-images.facade';
|
||||
import {themeImagesMockData} from '@store/theme-images/theme-images.facade.spec.mock';
|
||||
import {MenuItemComponent} from '@components/navbar/menu-item/menu-item.component';
|
||||
import {MenuItemLinkComponent} from '@components/navbar/menu-item-link/menu-item-link.component';
|
||||
import {MenuRecentlyViewedComponent} from '@components/navbar/menu-recently-viewed/menu-recently-viewed.component';
|
||||
|
||||
|
||||
const mockMenuItems = [
|
||||
{
|
||||
link: {
|
||||
url: '',
|
||||
label: 'Menu Item 1',
|
||||
route: '/fake-module-1',
|
||||
},
|
||||
icon: '',
|
||||
submenu: [],
|
||||
recentRecords: null
|
||||
},
|
||||
{
|
||||
link: {
|
||||
url: '',
|
||||
label: 'Menu Item 2',
|
||||
route: '/fake-module-2',
|
||||
},
|
||||
icon: '',
|
||||
submenu: [],
|
||||
recentRecords: null
|
||||
}
|
||||
];
|
||||
|
||||
@Component({
|
||||
selector: 'menu-item-list-test-host-component',
|
||||
template: '<scrm-menu-items-list [items]="items" [label]="label"></scrm-menu-items-list>'
|
||||
})
|
||||
class MenuItemListTestHostComponent {
|
||||
items = mockMenuItems;
|
||||
label = 'More';
|
||||
}
|
||||
|
||||
describe('MenuItemsListComponent', () => {
|
||||
let testHostComponent: MenuItemListTestHostComponent;
|
||||
let testHostFixture: ComponentFixture<MenuItemListTestHostComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [
|
||||
MenuItemComponent,
|
||||
MenuItemLinkComponent,
|
||||
MenuItemsListComponent,
|
||||
MenuRecentlyViewedComponent,
|
||||
MenuItemListTestHostComponent
|
||||
],
|
||||
imports: [
|
||||
AngularSvgIconModule,
|
||||
RouterTestingModule,
|
||||
HttpClientTestingModule,
|
||||
ImageModule,
|
||||
NgbModule,
|
||||
],
|
||||
providers: [
|
||||
{
|
||||
provide: ThemeImagesFacade, useValue: {
|
||||
images$: of(themeImagesMockData).pipe(take(1))
|
||||
}
|
||||
},
|
||||
],
|
||||
}).compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
testHostFixture = TestBed.createComponent(MenuItemListTestHostComponent);
|
||||
testHostComponent = testHostFixture.componentInstance;
|
||||
testHostFixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(testHostComponent).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should have label', () => {
|
||||
|
||||
const navItemLink = testHostFixture.nativeElement.querySelector('a');
|
||||
|
||||
expect(navItemLink.text).toContain('More');
|
||||
});
|
||||
|
||||
it('should have menu items', () => {
|
||||
|
||||
const navItemLink = testHostFixture.nativeElement.querySelector('div');
|
||||
const links = navItemLink.getElementsByClassName('action-link');
|
||||
|
||||
expect(links.length).toEqual(2);
|
||||
expect(links[0].textContent).toContain('Menu Item 1');
|
||||
expect(links[0].attributes.getNamedItem('href').value).toContain('/fake-module-1');
|
||||
expect(links[1].textContent).toContain('Menu Item 2');
|
||||
expect(links[1].attributes.getNamedItem('href').value).toContain('/fake-module-2');
|
||||
});
|
||||
});
|
|
@ -0,0 +1,15 @@
|
|||
import {Component, Input} from '@angular/core';
|
||||
import {MenuItem} from '@components/navbar/navbar.abstract';
|
||||
|
||||
@Component({
|
||||
selector: 'scrm-menu-items-list',
|
||||
templateUrl: './menu-items-list.component.html',
|
||||
styleUrls: []
|
||||
})
|
||||
export class MenuItemsListComponent {
|
||||
@Input() items: MenuItem[];
|
||||
@Input() label: string;
|
||||
|
||||
constructor() {
|
||||
}
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
<ng-container *ngIf="records && records.length">
|
||||
<h4 class="recently-viewed-header">{{languages.appStrings['LBL_LAST_VIEWED']}}</h4>
|
||||
<div *ngFor="let recentRecord of records" class="nav-item">
|
||||
<a class="nav-link action-link" [href]="recentRecord.url">{{ recentRecord.summary }}</a>
|
||||
</div>
|
||||
</ng-container>
|
|
@ -0,0 +1,103 @@
|
|||
import {async, ComponentFixture, TestBed} from '@angular/core/testing';
|
||||
import {of} from 'rxjs';
|
||||
import {take} from 'rxjs/operators';
|
||||
import {Component} from '@angular/core';
|
||||
import {RouterTestingModule} from '@angular/router/testing';
|
||||
import {HttpClientTestingModule} from '@angular/common/http/testing';
|
||||
import {AngularSvgIconModule} from 'angular-svg-icon';
|
||||
import {NgbModule} from '@ng-bootstrap/ng-bootstrap';
|
||||
|
||||
import {MenuRecentlyViewedComponent} from './menu-recently-viewed.component';
|
||||
import {LanguageStrings} from '@base/facades/language/language.facade';
|
||||
import {languageMockData} from '@base/facades/language/language.facade.spec.mock';
|
||||
import {MenuItemLinkComponent} from '@components/navbar/menu-item-link/menu-item-link.component';
|
||||
import {ImageModule} from '@components/image/image.module';
|
||||
import {ThemeImagesFacade} from '@base/facades/theme-images/theme-images.facade';
|
||||
import {themeImagesMockData} from '@base/facades/theme-images/theme-images.facade.spec.mock';
|
||||
|
||||
const recentRecords = [
|
||||
{
|
||||
summary: 'Module 1',
|
||||
url: '/fake-module-1'
|
||||
},
|
||||
{
|
||||
summary: 'Module 2',
|
||||
url: '/fake-module-2'
|
||||
}
|
||||
];
|
||||
|
||||
@Component({
|
||||
selector: 'menu-recently-viewed-test-host-component',
|
||||
template: '<scrm-menu-recently-viewed [records]="records" [languages]="languages"></scrm-menu-recently-viewed>'
|
||||
})
|
||||
class MenuRecentlyViewedTestHostComponent {
|
||||
records = recentRecords;
|
||||
languages: LanguageStrings = {
|
||||
...languageMockData,
|
||||
languageKey: 'en_us'
|
||||
};
|
||||
}
|
||||
|
||||
describe('MenuRecentlyViewedComponent', () => {
|
||||
let testHostComponent: MenuRecentlyViewedTestHostComponent;
|
||||
let testHostFixture: ComponentFixture<MenuRecentlyViewedTestHostComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [
|
||||
MenuItemLinkComponent,
|
||||
MenuRecentlyViewedComponent,
|
||||
MenuRecentlyViewedTestHostComponent
|
||||
],
|
||||
imports: [
|
||||
AngularSvgIconModule,
|
||||
RouterTestingModule,
|
||||
HttpClientTestingModule,
|
||||
ImageModule,
|
||||
NgbModule,
|
||||
],
|
||||
providers: [
|
||||
{
|
||||
provide: ThemeImagesFacade, useValue: {
|
||||
images$: of(themeImagesMockData).pipe(take(1))
|
||||
}
|
||||
},
|
||||
],
|
||||
}).compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
testHostFixture = TestBed.createComponent(MenuRecentlyViewedTestHostComponent);
|
||||
testHostComponent = testHostFixture.componentInstance;
|
||||
testHostFixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(testHostComponent).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should have label', () => {
|
||||
const title = testHostFixture.nativeElement.querySelector('h4');
|
||||
|
||||
expect(title.textContent).toContain('Recently Viewed');
|
||||
});
|
||||
|
||||
it('should have recently viewed record links', () => {
|
||||
|
||||
const navItemLinks = testHostFixture.nativeElement.getElementsByClassName('nav-item');
|
||||
|
||||
expect(navItemLinks.length).toEqual(2);
|
||||
|
||||
let links = navItemLinks[0].getElementsByClassName('nav-link');
|
||||
|
||||
expect(links.length).toEqual(1);
|
||||
expect(links[0].textContent).toContain('Module 1');
|
||||
expect(links[0].attributes.getNamedItem('href').value).toContain('/fake-module-1');
|
||||
|
||||
links = navItemLinks[1].getElementsByClassName('nav-link');
|
||||
|
||||
expect(links.length).toEqual(1);
|
||||
expect(links[0].textContent).toContain('Module 2');
|
||||
expect(links[0].attributes.getNamedItem('href').value).toContain('/fake-module-2');
|
||||
});
|
||||
});
|
|
@ -0,0 +1,16 @@
|
|||
import {Component, Input} from '@angular/core';
|
||||
import {LanguageStrings} from '@base/facades/language/language.facade';
|
||||
import {RecentRecordsMenuItem} from '@components/navbar/navbar.abstract';
|
||||
|
||||
@Component({
|
||||
selector: 'scrm-menu-recently-viewed',
|
||||
templateUrl: './menu-recently-viewed.component.html',
|
||||
styleUrls: []
|
||||
})
|
||||
export class MenuRecentlyViewedComponent {
|
||||
@Input() records: RecentRecordsMenuItem[];
|
||||
@Input() languages: LanguageStrings;
|
||||
|
||||
constructor() {
|
||||
}
|
||||
}
|
|
@ -2,10 +2,11 @@ import {ActionLinkModel} from './action-link-model';
|
|||
import {CurrentUserModel} from './current-user-model';
|
||||
import {AllMenuModel} from './all-menu-model';
|
||||
import {LogoModel} from '../logo/logo-model';
|
||||
import {GroupedTab, NavbarModuleMap, UserActionMenu} from '@base/facades/navigation/navigation.facade';
|
||||
import {LanguageListStringMap, LanguageStringMap} from '@base/facades/language/language.facade';
|
||||
import {GroupedTab, NavbarModuleMap, Navigation, UserActionMenu} from '@base/facades/navigation/navigation.facade';
|
||||
import {LanguageStrings, LanguageStringMap} from '@base/facades/language/language.facade';
|
||||
import {MenuItem} from '@components/navbar/navbar.abstract';
|
||||
import {UserPreferenceMap} from '@base/facades/user-preference/user-preference.facade';
|
||||
import {AppState} from '@base/facades/app-state/app-state.facade';
|
||||
|
||||
export interface NavbarModel {
|
||||
authenticated: boolean;
|
||||
|
@ -15,40 +16,26 @@ export interface NavbarModel {
|
|||
currentUser: CurrentUserModel;
|
||||
all: AllMenuModel;
|
||||
menu: MenuItem[];
|
||||
current?: MenuItem;
|
||||
|
||||
resetMenu(): void;
|
||||
|
||||
build(
|
||||
tabs: string[],
|
||||
modules: NavbarModuleMap,
|
||||
appStrings: LanguageStringMap,
|
||||
modStrings: LanguageListStringMap,
|
||||
appListStrings: LanguageListStringMap,
|
||||
menuItemThreshold: number,
|
||||
groupedTabs: GroupedTab[],
|
||||
navigation: Navigation,
|
||||
languages: LanguageStrings,
|
||||
userPreferences: UserPreferenceMap,
|
||||
userActionMenu: UserActionMenu[],
|
||||
currentUser: CurrentUserModel
|
||||
currentUser: CurrentUserModel,
|
||||
appState: AppState
|
||||
): void;
|
||||
|
||||
buildGroupTabMenu(
|
||||
items: string[],
|
||||
modules: NavbarModuleMap,
|
||||
appStrings: LanguageStringMap,
|
||||
modStrings: LanguageListStringMap,
|
||||
appListStrings: LanguageListStringMap,
|
||||
menuItemThreshold: number,
|
||||
languages: LanguageStrings,
|
||||
threshold: number,
|
||||
groupedTabs: GroupedTab[]
|
||||
): void;
|
||||
|
||||
buildTabMenu(
|
||||
items: string[],
|
||||
modules: NavbarModuleMap,
|
||||
appStrings: LanguageStringMap,
|
||||
modStrings: LanguageListStringMap,
|
||||
appListStrings: LanguageListStringMap,
|
||||
menuItemThreshold: number): void;
|
||||
|
||||
buildUserActionMenu(
|
||||
appStrings: LanguageStringMap,
|
||||
userActionMenu: UserActionMenu[],
|
||||
|
|
|
@ -1,25 +1,28 @@
|
|||
import {NavbarModel} from './navbar-model';
|
||||
import {LogoAbstract} from '../logo/logo-abstract';
|
||||
import {GroupedTab, NavbarModuleMap, UserActionMenu} from '@base/facades/navigation/navigation.facade';
|
||||
import {LanguageListStringMap, LanguageStringMap} from '@base/facades/language/language.facade';
|
||||
import {GroupedTab, NavbarModuleMap, Navigation, UserActionMenu} from '@base/facades/navigation/navigation.facade';
|
||||
import {LanguageStrings, LanguageStringMap} from '@base/facades/language/language.facade';
|
||||
|
||||
import {CurrentUserModel} from './current-user-model';
|
||||
import {ActionLinkModel} from './action-link-model';
|
||||
import {ready} from '@base/utils/object-utils';
|
||||
import {UserPreferenceMap} from '@base/facades/user-preference/user-preference.facade';
|
||||
import {AppState} from '@base/facades/app-state/app-state.facade';
|
||||
|
||||
export interface RecentRecordsMenuItem {
|
||||
summary: string;
|
||||
url: string;
|
||||
}
|
||||
|
||||
export interface MenuItemLink {
|
||||
label: string;
|
||||
url: string;
|
||||
route?: string;
|
||||
params?: { [key: string]: string };
|
||||
}
|
||||
|
||||
export interface MenuItem {
|
||||
link: {
|
||||
label: string;
|
||||
url: string;
|
||||
route?: string;
|
||||
params?: { [key: string]: string };
|
||||
};
|
||||
link: MenuItemLink;
|
||||
icon: string;
|
||||
submenu: MenuItem[];
|
||||
recentRecords?: RecentRecordsMenuItem[];
|
||||
|
@ -42,6 +45,11 @@ export class NavbarAbstract implements NavbarModel {
|
|||
extra: [],
|
||||
};
|
||||
menu: MenuItem[] = [];
|
||||
current?: MenuItem;
|
||||
|
||||
/**
|
||||
* Public API
|
||||
*/
|
||||
|
||||
/**
|
||||
* Reset menus
|
||||
|
@ -51,8 +59,16 @@ export class NavbarAbstract implements NavbarModel {
|
|||
this.globalActions = [];
|
||||
this.all.modules = [];
|
||||
this.all.extra = [];
|
||||
this.current = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build user action menu
|
||||
*
|
||||
* @param {{}} appStrings map
|
||||
* @param {[]} userActionMenu info
|
||||
* @param {{}} currentUser info
|
||||
*/
|
||||
public buildUserActionMenu(
|
||||
appStrings: LanguageStringMap,
|
||||
userActionMenu: UserActionMenu[],
|
||||
|
@ -64,11 +80,11 @@ export class NavbarAbstract implements NavbarModel {
|
|||
|
||||
if (userActionMenu) {
|
||||
userActionMenu.forEach((subMenu) => {
|
||||
let name = subMenu.name;
|
||||
const name = subMenu.name;
|
||||
let url = subMenu.url;
|
||||
let urlParams;
|
||||
|
||||
if (name == 'logout') {
|
||||
if (name === 'logout') {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -77,12 +93,12 @@ export class NavbarAbstract implements NavbarModel {
|
|||
url = ROUTE_PREFIX + '/' + (urlParams.module).toLowerCase() + '/' + (urlParams.action).toLowerCase();
|
||||
}
|
||||
|
||||
let label = appStrings[subMenu.labelKey];
|
||||
const label = appStrings[subMenu.labelKey];
|
||||
|
||||
this.globalActions.push({
|
||||
link: {
|
||||
url: url,
|
||||
label: label,
|
||||
url,
|
||||
label,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
@ -92,76 +108,65 @@ export class NavbarAbstract implements NavbarModel {
|
|||
|
||||
/**
|
||||
* Build navbar
|
||||
* @param tabs
|
||||
* @param modules
|
||||
* @param appStrings
|
||||
* @param modStrings
|
||||
* @param appListStrings
|
||||
* @param menuItemThreshold
|
||||
* @param groupedTabs
|
||||
* @param userPreferences
|
||||
* @param userActionMenu
|
||||
* @param currentUser
|
||||
*
|
||||
* @param {{}} navigation info
|
||||
* @param {{}} language map
|
||||
* @param {{}} userPreferences info
|
||||
* @param {{}} currentUser info
|
||||
* @param {{}} appState info
|
||||
*/
|
||||
public build(
|
||||
tabs: string[],
|
||||
modules: NavbarModuleMap,
|
||||
appStrings: LanguageStringMap,
|
||||
modStrings: LanguageListStringMap,
|
||||
appListStrings: LanguageListStringMap,
|
||||
menuItemThreshold: number,
|
||||
groupedTabs: GroupedTab[],
|
||||
navigation: Navigation,
|
||||
language: LanguageStrings,
|
||||
userPreferences: UserPreferenceMap,
|
||||
userActionMenu: UserActionMenu[],
|
||||
currentUser: CurrentUserModel,
|
||||
appState: AppState
|
||||
): void {
|
||||
|
||||
this.resetMenu();
|
||||
|
||||
if (!ready([tabs, modules, appStrings, modStrings, appListStrings, userPreferences])) {
|
||||
if (!ready([language.appStrings, language.modStrings, language.appListStrings, userPreferences, currentUser])) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.buildUserActionMenu(appStrings, userActionMenu, currentUser);
|
||||
this.buildUserActionMenu(language.appStrings, navigation.userActionMenu, currentUser);
|
||||
|
||||
const navigationParadigm = userPreferences.navigation_paradigm;
|
||||
const navigationParadigm = userPreferences.navigation_paradigm.toString();
|
||||
|
||||
if (navigationParadigm.toString() === 'm') {
|
||||
this.buildTabMenu(tabs, modules, appStrings, modStrings, appListStrings, menuItemThreshold);
|
||||
if (navigationParadigm === 'm') {
|
||||
this.buildModuleNavigation(navigation, language, appState);
|
||||
return;
|
||||
}
|
||||
|
||||
if (navigationParadigm.toString() === 'gm') {
|
||||
this.buildGroupTabMenu(tabs, modules, appStrings, modStrings, appListStrings, menuItemThreshold, groupedTabs);
|
||||
if (navigationParadigm === 'gm') {
|
||||
this.buildGroupedNavigation(navigation, language, appState);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Build Group tab menu
|
||||
* @param items
|
||||
* @param modules
|
||||
* @param appStrings
|
||||
* @param modStrings
|
||||
* @param appListStrings
|
||||
* @param threshold
|
||||
* @param groupedTabs
|
||||
*
|
||||
* @param {[]} items list
|
||||
* @param {{}} modules info
|
||||
* @param {{}} languages map
|
||||
* @param {number} threshold limit
|
||||
* @param {{}} groupedTabs info
|
||||
*/
|
||||
public buildGroupTabMenu(
|
||||
items: string[],
|
||||
modules: NavbarModuleMap,
|
||||
appStrings: LanguageStringMap,
|
||||
modStrings: LanguageListStringMap,
|
||||
appListStrings: LanguageListStringMap,
|
||||
languages: LanguageStrings,
|
||||
threshold: number,
|
||||
groupedTabs: GroupedTab[]): void {
|
||||
groupedTabs: GroupedTab[]
|
||||
): void {
|
||||
|
||||
const navItems = [];
|
||||
const moreItems = [];
|
||||
|
||||
if (items && items.length > 0) {
|
||||
items.forEach((module) => {
|
||||
moreItems.push(this.buildTabMenuItem(module, modules[module], appStrings, modStrings, appListStrings));
|
||||
moreItems.push(this.buildTabMenuItem(module, modules[module], languages));
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -173,9 +178,7 @@ export class NavbarAbstract implements NavbarModel {
|
|||
groupedTab.labelKey,
|
||||
groupedTab.modules,
|
||||
modules,
|
||||
appStrings,
|
||||
modStrings,
|
||||
appListStrings
|
||||
languages
|
||||
));
|
||||
}
|
||||
|
||||
|
@ -187,20 +190,89 @@ export class NavbarAbstract implements NavbarModel {
|
|||
}
|
||||
|
||||
/**
|
||||
* Build tab / module menu
|
||||
* @param items
|
||||
* @param modules
|
||||
* @param appStrings
|
||||
* @param modStrings
|
||||
* @param appListStrings
|
||||
* @param threshold
|
||||
*
|
||||
* Internal API
|
||||
*
|
||||
*/
|
||||
public buildTabMenu(items: string[],
|
||||
modules: NavbarModuleMap,
|
||||
appStrings: LanguageStringMap,
|
||||
modStrings: LanguageListStringMap,
|
||||
appListStrings: LanguageListStringMap,
|
||||
threshold: number
|
||||
|
||||
/**
|
||||
* Build module navigation
|
||||
*
|
||||
* @param {{}} navigation info
|
||||
* @param {{}} languages map
|
||||
* @param {{}} appState info
|
||||
*/
|
||||
protected buildModuleNavigation(
|
||||
navigation: Navigation,
|
||||
languages: LanguageStrings,
|
||||
appState: AppState
|
||||
): void {
|
||||
|
||||
if (!ready([navigation.tabs, navigation.modules])) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.buildTabMenu(navigation.tabs, navigation.modules, languages, navigation.maxTabs, appState);
|
||||
this.buildSelectedModule(navigation, languages, appState);
|
||||
}
|
||||
|
||||
/**
|
||||
* Build grouped navigation
|
||||
*
|
||||
* @param {{}} navigation info
|
||||
* @param {{}} languages map
|
||||
* @param {{}} appState info
|
||||
*/
|
||||
protected buildGroupedNavigation(
|
||||
navigation: Navigation,
|
||||
languages: LanguageStrings,
|
||||
appState: AppState
|
||||
): void {
|
||||
|
||||
if (!ready([navigation.tabs, navigation.modules, navigation.groupedTabs])) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.buildGroupTabMenu(navigation.tabs, navigation.modules, languages, navigation.maxTabs, navigation.groupedTabs);
|
||||
this.buildSelectedModule(navigation, languages, appState);
|
||||
}
|
||||
|
||||
/**
|
||||
* Build selected module
|
||||
*
|
||||
* @param {{}} navigation info
|
||||
* @param {{}} languages map
|
||||
* @param {{}} appState info
|
||||
*/
|
||||
protected buildSelectedModule(navigation: Navigation, languages: LanguageStrings, appState: AppState): void {
|
||||
if (!appState || !appState.module || appState.module === 'home') {
|
||||
return;
|
||||
}
|
||||
|
||||
const module = appState.module;
|
||||
|
||||
if (!navigation.modules[module]){
|
||||
return;
|
||||
}
|
||||
|
||||
this.current = this.buildTabMenuItem(module, navigation.modules[module], languages);
|
||||
}
|
||||
|
||||
/**
|
||||
* Build tab / module menu
|
||||
*
|
||||
* @param {[]} items list
|
||||
* @param {{}} modules info
|
||||
* @param {{}} languages map
|
||||
* @param {number} threshold limit
|
||||
* @param {{}} appState info
|
||||
*/
|
||||
protected buildTabMenu(
|
||||
items: string[],
|
||||
modules: NavbarModuleMap,
|
||||
languages: LanguageStrings,
|
||||
threshold: number,
|
||||
appState: AppState
|
||||
): void {
|
||||
|
||||
const navItems = [];
|
||||
|
@ -219,10 +291,12 @@ export class NavbarAbstract implements NavbarModel {
|
|||
return;
|
||||
}
|
||||
|
||||
if (count <= threshold) {
|
||||
navItems.push(this.buildTabMenuItem(module, modules[module], appStrings, modStrings, appListStrings));
|
||||
const item = this.buildTabMenuItem(module, modules[module], languages);
|
||||
|
||||
if (appState.module === module || count >= threshold){
|
||||
moreItems.push(item);
|
||||
} else {
|
||||
moreItems.push(this.buildTabMenuItem(module, modules[module], appStrings, modStrings, appListStrings));
|
||||
navItems.push(item);
|
||||
}
|
||||
|
||||
count++;
|
||||
|
@ -232,22 +306,22 @@ export class NavbarAbstract implements NavbarModel {
|
|||
this.all.modules = moreItems;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Build Grouped Tab menu item
|
||||
* @param moduleLabel to display
|
||||
* @param groupedModules list
|
||||
* @param modules list
|
||||
* @param appStrings list
|
||||
* @param modStrings list
|
||||
* @param appListStrings list
|
||||
*
|
||||
* @param {string} moduleLabel to display
|
||||
* @param {{}} groupedModules list
|
||||
* @param {{}} modules list
|
||||
* @param {{}} languages map
|
||||
*
|
||||
* @returns {{}} group tab menu item
|
||||
*/
|
||||
public buildTabGroupedMenuItem(
|
||||
protected buildTabGroupedMenuItem(
|
||||
moduleLabel: string,
|
||||
groupedModules: any[],
|
||||
modules: NavbarModuleMap,
|
||||
appStrings: LanguageStringMap,
|
||||
modStrings: LanguageListStringMap,
|
||||
appListStrings: LanguageListStringMap
|
||||
languages: LanguageStrings
|
||||
): any {
|
||||
let moduleUrl = '';
|
||||
|
||||
|
@ -260,30 +334,29 @@ export class NavbarAbstract implements NavbarModel {
|
|||
|
||||
return {
|
||||
link: {
|
||||
label: (appStrings && appStrings[moduleLabel]) || moduleLabel,
|
||||
label: (languages.appStrings && languages.appStrings[moduleLabel]) || moduleLabel,
|
||||
url: moduleUrl,
|
||||
route: moduleRoute,
|
||||
params: null
|
||||
},
|
||||
icon: '',
|
||||
submenu: this.buildGroupedMenu(groupedModules, modules, appStrings, modStrings, appListStrings)
|
||||
submenu: this.buildGroupedMenu(groupedModules, modules, languages)
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Build Grouped menu
|
||||
* @param groupedModules
|
||||
* @param modules
|
||||
* @param appStrings
|
||||
* @param modStrings
|
||||
* @param appListStrings
|
||||
*
|
||||
* @param {{}} groupedModules info
|
||||
* @param {{}} modules map
|
||||
* @param {{}} languages maps
|
||||
*
|
||||
* @returns {[]} menu item array
|
||||
*/
|
||||
public buildGroupedMenu(
|
||||
protected buildGroupedMenu(
|
||||
groupedModules: any[],
|
||||
modules: NavbarModuleMap,
|
||||
appStrings: LanguageStringMap,
|
||||
modStrings: LanguageListStringMap,
|
||||
appListStrings: LanguageListStringMap
|
||||
languages: LanguageStrings,
|
||||
): MenuItem[] {
|
||||
|
||||
const groupedItems = [];
|
||||
|
@ -296,7 +369,7 @@ export class NavbarAbstract implements NavbarModel {
|
|||
return;
|
||||
}
|
||||
|
||||
groupedItems.push(this.buildTabMenuItem(groupedModule, module, appStrings, modStrings, appListStrings));
|
||||
groupedItems.push(this.buildTabMenuItem(groupedModule, module, languages));
|
||||
});
|
||||
|
||||
return groupedItems;
|
||||
|
@ -304,31 +377,30 @@ export class NavbarAbstract implements NavbarModel {
|
|||
|
||||
/**
|
||||
* Build module menu items
|
||||
* @param module
|
||||
* @param moduleInfo
|
||||
* @param appStrings
|
||||
* @param modStrings
|
||||
* @param appListStrings
|
||||
*
|
||||
* @param {string} module name
|
||||
* @param {{}} moduleInfo info
|
||||
* @param {{}} languages object
|
||||
*
|
||||
* @returns {{}} menuItem
|
||||
*/
|
||||
public buildTabMenuItem(
|
||||
protected buildTabMenuItem(
|
||||
module: string,
|
||||
moduleInfo: any,
|
||||
appStrings: LanguageStringMap,
|
||||
modStrings: LanguageListStringMap,
|
||||
appListStrings: LanguageListStringMap
|
||||
languages: LanguageStrings,
|
||||
): MenuItem {
|
||||
|
||||
let moduleUrl = (moduleInfo && moduleInfo.defaultRoute) || moduleInfo.defaultRoute;
|
||||
let moduleUrl = (moduleInfo && moduleInfo.defaultRoute) || '';
|
||||
let moduleRoute = null;
|
||||
if (moduleUrl.startsWith(ROUTE_PREFIX)) {
|
||||
moduleRoute = moduleUrl.replace(ROUTE_PREFIX, '');
|
||||
moduleUrl = null;
|
||||
}
|
||||
|
||||
const moduleLabel = (moduleInfo && moduleInfo.labelKey) || moduleInfo.labelKey;
|
||||
const moduleLabel = (moduleInfo && moduleInfo.labelKey) || '';
|
||||
const menuItem = {
|
||||
link: {
|
||||
label: (appListStrings && appListStrings.moduleList[moduleInfo.labelKey]) || moduleLabel,
|
||||
label: (languages.appListStrings && languages.appListStrings.moduleList[moduleInfo.labelKey]) || moduleLabel,
|
||||
url: moduleUrl,
|
||||
route: moduleRoute,
|
||||
params: null
|
||||
|
@ -339,10 +411,10 @@ export class NavbarAbstract implements NavbarModel {
|
|||
|
||||
if (moduleInfo) {
|
||||
moduleInfo.menu.forEach((subMenu) => {
|
||||
let label = modStrings[module][subMenu.labelKey];
|
||||
let label = languages.modStrings[module][subMenu.labelKey];
|
||||
|
||||
if (!label) {
|
||||
label = appStrings[subMenu.labelKey];
|
||||
label = languages.appStrings[subMenu.labelKey];
|
||||
}
|
||||
|
||||
let actionUrl = subMenu.url;
|
||||
|
@ -375,15 +447,18 @@ export class NavbarAbstract implements NavbarModel {
|
|||
}
|
||||
|
||||
/**
|
||||
* @param search
|
||||
* Get module from url params
|
||||
*
|
||||
* @param {string} search query
|
||||
* @returns {{}} params map
|
||||
*/
|
||||
private getModuleFromUrlParams(search) {
|
||||
const hashes = search.slice(search.indexOf('?') + 1).split('&')
|
||||
const params = {}
|
||||
private getModuleFromUrlParams(search: string): any {
|
||||
const hashes = search.slice(search.indexOf('?') + 1).split('&');
|
||||
const params = {};
|
||||
hashes.map(hash => {
|
||||
const [key, val] = hash.split('=')
|
||||
params[key] = decodeURIComponent(val)
|
||||
})
|
||||
return params
|
||||
const [key, val] = hash.split('=');
|
||||
params[key] = decodeURIComponent(val);
|
||||
});
|
||||
return params;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -117,76 +117,35 @@
|
|||
<ng-container *ngIf="this.isUserLoggedIn && !mobileNavbar">
|
||||
<nav class="navbar navbar-expand-md navbar-1">
|
||||
<div class="navbar-collapse collapse collapsenav" [ngbCollapse]="mainNavCollapse">
|
||||
<ul class="navbar-nav home-nav">
|
||||
<li class="nav-item home-nav-link">
|
||||
<a class="home-nav-link" [routerLink]="systemConfigFacade.getHomePage()">
|
||||
<scrm-image class="home-icon" image="home"></scrm-image>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
<scrm-home-menu-item
|
||||
[route]="getHomePage()"
|
||||
[active]="vm.appState.module && vm.appState.module === 'home'"
|
||||
></scrm-home-menu-item>
|
||||
|
||||
<!-- Navbar with grouped tabs -->
|
||||
|
||||
<ng-container *ngIf="vm.userPreferences['navigation_paradigm'] == 'gm'">
|
||||
|
||||
<ul class="navbar-nav grouped">
|
||||
|
||||
<li class="top-nav nav-item dropdown non-grouped active" *ngIf="navbar.current">
|
||||
<scrm-menu-item [languages]="vm.languages" [item]="navbar.current"></scrm-menu-item>
|
||||
</li>
|
||||
|
||||
<li class="top-nav nav-item dropdown main-grouped" *ngFor="let item of navbar.menu">
|
||||
<span data-toggle="collapse" data-target=".navbar-collapse">
|
||||
<a class="nav-link-grouped dropdown-toggle" data-toggle="dropdown"
|
||||
href="{{ item.link.route }}">
|
||||
{{ item.link.label }}
|
||||
</a>
|
||||
</span>
|
||||
<ul class="dropdown-menu main" aria-labelledby="navbarDropdownMenuLink"
|
||||
[ngbCollapse]="subNavCollapse">
|
||||
<li class="nav-item dropdown-submenu submenu" *ngFor="let sub of item.submenu">
|
||||
<a class="nav-link action-link" *ngIf="!sub.submenu.length"
|
||||
[href]="sub.link.url"
|
||||
[routerLink]="sub.link.route"
|
||||
[queryParams]="sub.link.params">
|
||||
{{ sub.link.label }}
|
||||
</a>
|
||||
<a class="nav-link action-link dropdown-item dropdown-toggle"
|
||||
*ngIf="sub.submenu.length"
|
||||
(click)="subItemCollapse = !subItemCollapse">
|
||||
{{ sub.link.label }}
|
||||
</a>
|
||||
<ul *ngIf="sub.submenu.length" class="dropdown-menu submenu">
|
||||
<li *ngFor="let subitem of sub.submenu" class="nav-item">
|
||||
<a class="nav-link action-link"
|
||||
[href]="subitem.link.url"
|
||||
[routerLink]="subitem.link.route"
|
||||
[queryParams]="subitem.link.params">
|
||||
<scrm-image *ngIf="subitem.icon" image="{{ subitem.icon }}">
|
||||
</scrm-image>
|
||||
{{ subitem.link.label }}
|
||||
</a>
|
||||
</li>
|
||||
<ng-template [ngIf]="sub.recentRecords && sub.recentRecords.length">
|
||||
<h4 class="recently-viewed-header">RECENTLY VIEWED</h4>
|
||||
<li class="nav-item" *ngFor="let rec of sub.recentRecords">
|
||||
<a class="nav-link action-link"
|
||||
href="{{ rec.url }}">{{ rec.summary }}</a>
|
||||
</li>
|
||||
</ng-template>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
<scrm-grouped-menu-item
|
||||
[item]="item"
|
||||
[languages]="vm.languages"
|
||||
[subNavCollapse]="subNavCollapse"
|
||||
>
|
||||
</scrm-grouped-menu-item>
|
||||
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<ul *ngIf="navbar.all.modules && navbar.all.modules.length > 0"
|
||||
class="navbar-nav">
|
||||
<li class="top-nav nav-item dropdown non-grouped">
|
||||
<a class="nav-link-nongrouped dropdown-toggle">{{vm.appStrings['LBL_TABGROUP_ALL']}}</a>
|
||||
<div aria-labelledby="navbarDropdownMenuLink" class="dropdown-menu more-menu submenu">
|
||||
<div class="nav-item" *ngFor="let item of navbar.all.modules">
|
||||
<a class="nav-link action-link" [href]="item.link.url"
|
||||
[routerLink]="item.link.route">{{ item.link.label }}</a>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
<scrm-menu-items-list [items]="navbar.all.modules"
|
||||
[label]="vm.languages.appStrings['LBL_TABGROUP_ALL']">
|
||||
</scrm-menu-items-list>
|
||||
|
||||
</ng-container>
|
||||
|
||||
|
@ -198,54 +157,17 @@
|
|||
<ng-container *ngIf="vm.userPreferences['navigation_paradigm'] != 'gm'">
|
||||
|
||||
<ul class="navbar-nav">
|
||||
<li class="top-nav nav-item dropdown non-grouped active" *ngIf="navbar.current">
|
||||
<scrm-menu-item [languages]="vm.languages" [item]="navbar.current"></scrm-menu-item>
|
||||
</li>
|
||||
<li class="top-nav nav-item dropdown non-grouped" *ngFor="let item of navbar.menu">
|
||||
|
||||
<!-- TODO: Implement a cleaner solution than hard coded ngIf -->
|
||||
|
||||
<ng-template [ngIf]="item.link.label !== 'Home'">
|
||||
<span data-toggle="collapse" data-target=".navbar-collapse">
|
||||
<a class="nav-link-nongrouped dropdown-toggle"
|
||||
[href]="item.link.url"
|
||||
[routerLink]="item.link.route"
|
||||
[queryParams]="item.link.params"
|
||||
routerLinkActive="active">
|
||||
{{ item.link.label }}
|
||||
</a>
|
||||
</span>
|
||||
<div aria-labelledby="navbarDropdownMenuLink" class="dropdown-menu submenu">
|
||||
<div class="nav-item" *ngFor="let sub of item.submenu">
|
||||
<a class="nav-link action-link"
|
||||
[href]="sub.link.url"
|
||||
[routerLink]="sub.link.route"
|
||||
[queryParams]="sub.link.params">
|
||||
<scrm-image *ngIf="sub.icon"
|
||||
image="{{ sub.icon }}"></scrm-image>
|
||||
{{ sub.link.label }}
|
||||
</a>
|
||||
</div>
|
||||
<ng-template [ngIf]="item.recentRecords && item.recentRecords.length">
|
||||
<h4 class="recently-viewed-header">{{vm.appStrings['LBL_LAST_VIEWED']}}</h4>
|
||||
<div *ngFor="let recentRecord of item.recentRecords" class="nav-item">
|
||||
<a class="nav-link action-link" href="#">{{ recentRecord.summary }}</a>
|
||||
</div>
|
||||
</ng-template>
|
||||
</div>
|
||||
</ng-template>
|
||||
<scrm-menu-item [languages]="vm.languages" [item]="item"></scrm-menu-item>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<ul *ngIf="navbar.all.modules && navbar.all.modules.length > 0"
|
||||
class="navbar-nav">
|
||||
<li class="top-nav nav-item dropdown non-grouped">
|
||||
<a class="nav-link-nongrouped dropdown-toggle">{{vm.appStrings['LBL_MORE']}}</a>
|
||||
<div aria-labelledby="navbarDropdownMenuLink" class="dropdown-menu more-menu submenu">
|
||||
<div class="nav-item" *ngFor="let item of navbar.all.modules">
|
||||
<a class="nav-link action-link" [href]="item.link.url"
|
||||
[routerLink]="item.link.route">{{ item.link.label }}</a>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
<scrm-menu-items-list [items]="navbar.all.modules"
|
||||
[label]="vm.languages.appStrings['LBL_MORE']">
|
||||
</scrm-menu-items-list>
|
||||
|
||||
</ng-container>
|
||||
|
||||
|
|
|
@ -47,18 +47,6 @@ describe('NavbarUiComponent', () => {
|
|||
expect(component).toBeTruthy();
|
||||
});
|
||||
|
||||
/*
|
||||
it('should get metadata', async (() => {
|
||||
fixture.detectChanges(); // onInit()
|
||||
|
||||
fixture.whenStable().then(() => {
|
||||
expect(component.navbar).toEqual(jasmine.objectContaining(navigationMockData.navbar));
|
||||
});
|
||||
|
||||
component.ngOnInit();
|
||||
}));
|
||||
|
||||
*/
|
||||
});
|
||||
|
||||
});
|
||||
|
|
|
@ -5,11 +5,12 @@ import {map} from 'rxjs/operators';
|
|||
import {ApiService} from '@services/api/api.service';
|
||||
import {NavbarModel} from './navbar-model';
|
||||
import {NavbarAbstract} from './navbar.abstract';
|
||||
import {NavbarModuleMap, NavigationFacade} from '@base/facades/navigation/navigation.facade';
|
||||
import {LanguageFacade, LanguageListStringMap, LanguageStringMap} from '@base/facades/language/language.facade';
|
||||
import {Navigation, NavigationFacade} from '@base/facades/navigation/navigation.facade';
|
||||
import {UserPreferenceFacade, UserPreferenceMap} from '@base/facades/user-preference/user-preference.facade';
|
||||
import {AuthService} from '@services/auth/auth.service';
|
||||
import {SystemConfigFacade} from '@base/facades/system-config/system-config.facade';
|
||||
import {AppState, AppStateFacade} from '@base/facades/app-state/app-state.facade';
|
||||
import {LanguageFacade, LanguageStrings,} from '@base/facades/language/language.facade';
|
||||
|
||||
@Component({
|
||||
selector: 'scrm-navbar-ui',
|
||||
|
@ -24,7 +25,6 @@ export class NavbarUiComponent implements OnInit, OnDestroy {
|
|||
isUserLoggedIn: boolean;
|
||||
|
||||
mainNavCollapse = true;
|
||||
subItemCollapse = true;
|
||||
subNavCollapse = true;
|
||||
mobileNavbar = false;
|
||||
mobileSubNav = false;
|
||||
|
@ -34,55 +34,41 @@ export class NavbarUiComponent implements OnInit, OnDestroy {
|
|||
|
||||
navbar: NavbarModel = new NavbarAbstract();
|
||||
|
||||
tabs$: Observable<string[]> = this.navigationFacade.tabs$;
|
||||
modules$: Observable<NavbarModuleMap> = this.navigationFacade.modules$;
|
||||
appStrings$: Observable<LanguageStringMap> = this.languageFacade.appStrings$;
|
||||
modStrings$: Observable<LanguageListStringMap> = this.languageFacade.modStrings$;
|
||||
appListStrings$: Observable<LanguageListStringMap> = this.languageFacade.appListStrings$;
|
||||
languages$: Observable<LanguageStrings> = this.languageFacade.vm$;
|
||||
userPreferences$: Observable<UserPreferenceMap> = this.userPreferenceFacade.userPreferences$;
|
||||
groupedTabs$: Observable<any> = this.navigationFacade.groupedTabs$;
|
||||
userActionMenu$: Observable<any> = this.navigationFacade.userActionMenu$;
|
||||
currentUser$: Observable<any> = this.authService.currentUser$;
|
||||
appState$: Observable<AppState> = this.appState.vm$;
|
||||
navigation$: Observable<Navigation> = this.navigationFacade.vm$;
|
||||
|
||||
vm$ = combineLatest([
|
||||
this.tabs$,
|
||||
this.modules$,
|
||||
this.appStrings$,
|
||||
this.appListStrings$,
|
||||
this.modStrings$,
|
||||
this.navigation$,
|
||||
this.languages$,
|
||||
this.userPreferences$,
|
||||
this.groupedTabs$,
|
||||
this.userActionMenu$,
|
||||
this.currentUser$
|
||||
this.currentUser$,
|
||||
this.appState$
|
||||
]).pipe(
|
||||
map(([tabs, modules, appStrings, appListStrings, modStrings, userPreferences, groupedTabs, userActionMenu, currentUser]) => {
|
||||
map(([navigation, languages, userPreferences, currentUser, appState]) => {
|
||||
|
||||
this.navbar.build(
|
||||
tabs,
|
||||
modules,
|
||||
appStrings,
|
||||
modStrings,
|
||||
appListStrings,
|
||||
this.menuItemThreshold,
|
||||
groupedTabs,
|
||||
navigation,
|
||||
languages,
|
||||
userPreferences,
|
||||
userActionMenu,
|
||||
currentUser
|
||||
currentUser,
|
||||
appState
|
||||
);
|
||||
|
||||
return {
|
||||
tabs, modules, appStrings, appListStrings, modStrings, userPreferences, groupedTabs
|
||||
navigation, languages, userPreferences, appState
|
||||
};
|
||||
})
|
||||
);
|
||||
|
||||
protected menuItemThreshold = 5;
|
||||
|
||||
constructor(protected navigationFacade: NavigationFacade,
|
||||
protected languageFacade: LanguageFacade,
|
||||
protected api: ApiService,
|
||||
protected userPreferenceFacade: UserPreferenceFacade,
|
||||
protected systemConfigFacade: SystemConfigFacade,
|
||||
protected appState: AppStateFacade,
|
||||
private authService: AuthService
|
||||
) {
|
||||
const navbar = new NavbarAbstract();
|
||||
|
@ -91,6 +77,13 @@ export class NavbarUiComponent implements OnInit, OnDestroy {
|
|||
NavbarUiComponent.instances.push(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Public API
|
||||
*/
|
||||
|
||||
/**
|
||||
* Reset component instance
|
||||
*/
|
||||
static reset(): void {
|
||||
NavbarUiComponent.instances.forEach((navbarComponent: NavbarUiComponent) => {
|
||||
navbarComponent.loaded = false;
|
||||
|
@ -98,19 +91,6 @@ export class NavbarUiComponent implements OnInit, OnDestroy {
|
|||
});
|
||||
}
|
||||
|
||||
public changeSubNav(event: Event, parentNavItem): void {
|
||||
this.mobileSubNav = !this.mobileSubNav;
|
||||
this.backLink = !this.backLink;
|
||||
this.mainNavLink = !this.mainNavLink;
|
||||
this.submenu = parentNavItem.submenu;
|
||||
}
|
||||
|
||||
public navBackLink(): void {
|
||||
this.mobileSubNav = !this.mobileSubNav;
|
||||
this.backLink = !this.backLink;
|
||||
this.mainNavLink = !this.mainNavLink;
|
||||
}
|
||||
|
||||
@HostListener('window:resize', ['$event'])
|
||||
onResize(event: any): void {
|
||||
const innerWidth = event.target.innerWidth;
|
||||
|
@ -131,11 +111,56 @@ export class NavbarUiComponent implements OnInit, OnDestroy {
|
|||
this.authService.isUserLoggedIn.unsubscribe();
|
||||
}
|
||||
|
||||
/**
|
||||
* Change subnavigation
|
||||
*
|
||||
* @param {{}} event triggered
|
||||
* @param {{}} parentNavItem parent
|
||||
*/
|
||||
public changeSubNav(event: Event, parentNavItem): void {
|
||||
this.mobileSubNav = !this.mobileSubNav;
|
||||
this.backLink = !this.backLink;
|
||||
this.mainNavLink = !this.mainNavLink;
|
||||
this.submenu = parentNavItem.submenu;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set link flags
|
||||
*/
|
||||
public navBackLink(): void {
|
||||
this.mobileSubNav = !this.mobileSubNav;
|
||||
this.backLink = !this.backLink;
|
||||
this.mainNavLink = !this.mainNavLink;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get home page
|
||||
*
|
||||
* @returns {string} homepage
|
||||
*/
|
||||
public getHomePage(): string {
|
||||
return this.systemConfigFacade.getHomePage();
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal API
|
||||
*/
|
||||
|
||||
/**
|
||||
* Set navbar model
|
||||
*
|
||||
* @param {{}} navbar model
|
||||
*/
|
||||
protected setNavbar(navbar: NavbarModel): void {
|
||||
this.navbar = navbar;
|
||||
this.loaded = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if is loaded
|
||||
*
|
||||
* @returns {{boolean}} is loaded
|
||||
*/
|
||||
protected isLoaded(): boolean {
|
||||
return this.loaded;
|
||||
}
|
||||
|
|
|
@ -10,11 +10,30 @@ import {ActionBarUiModule} from '../action-bar/action-bar.module';
|
|||
import {NgbModule} from '@ng-bootstrap/ng-bootstrap';
|
||||
import {RouterModule} from '@angular/router';
|
||||
import {ImageModule} from '@components/image/image.module';
|
||||
import {MenuItemComponent} from '@components/navbar/menu-item/menu-item.component';
|
||||
import {MenuRecentlyViewedComponent} from '@components/navbar/menu-recently-viewed/menu-recently-viewed.component';
|
||||
import {HomeMenuItemComponent} from '@components/navbar/home-menu-item/home-menu-item.component';
|
||||
import { MenuItemLinkComponent } from './menu-item-link/menu-item-link.component';
|
||||
import { GroupedMenuItemComponent } from './grouped-menu-item/grouped-menu-item.component';
|
||||
import { MenuItemsListComponent } from './menu-items-list/menu-items-list.component';
|
||||
|
||||
|
||||
@NgModule({
|
||||
declarations: [NavbarUiComponent],
|
||||
exports: [NavbarUiComponent],
|
||||
declarations: [
|
||||
NavbarUiComponent,
|
||||
MenuItemComponent,
|
||||
MenuRecentlyViewedComponent,
|
||||
HomeMenuItemComponent,
|
||||
MenuItemLinkComponent,
|
||||
GroupedMenuItemComponent,
|
||||
MenuItemsListComponent
|
||||
],
|
||||
exports: [
|
||||
NavbarUiComponent,
|
||||
MenuItemComponent,
|
||||
MenuRecentlyViewedComponent,
|
||||
HomeMenuItemComponent
|
||||
],
|
||||
imports: [
|
||||
CommonModule,
|
||||
AppManagerModule.forChild(NavbarUiComponent),
|
||||
|
|
|
@ -13,13 +13,25 @@ describe('AppState Facade', () => {
|
|||
injector = getTestBed();
|
||||
});
|
||||
|
||||
it('#updateLoading',
|
||||
(done: DoneFn) => {
|
||||
service.updateLoading('test', true);
|
||||
service.loading$.pipe(take(1)).subscribe(loading => {
|
||||
expect(loading).toEqual(true);
|
||||
done();
|
||||
});
|
||||
it('#updateLoading', () => {
|
||||
service.updateLoading('test', true);
|
||||
service.loading$.pipe(take(1)).subscribe(loading => {
|
||||
expect(loading).toEqual(true);
|
||||
});
|
||||
});
|
||||
|
||||
it('#setModule', () => {
|
||||
service.setModule('accounts');
|
||||
service.module$.pipe(take(1)).subscribe(module => {
|
||||
expect(module).toEqual('accounts');
|
||||
}).unsubscribe();
|
||||
});
|
||||
|
||||
it('#setView', () => {
|
||||
service.setView('record');
|
||||
service.view$.pipe(take(1)).subscribe(view => {
|
||||
expect(view).toEqual('record');
|
||||
}).unsubscribe();
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -1,16 +1,20 @@
|
|||
import {Injectable} from '@angular/core';
|
||||
import {BehaviorSubject, combineLatest, Observable} from 'rxjs';
|
||||
import {distinctUntilChanged, map} from 'rxjs/operators';
|
||||
import {BehaviorSubject, Observable, combineLatest} from 'rxjs';
|
||||
import {map, distinctUntilChanged} from 'rxjs/operators';
|
||||
import {deepClone} from '@base/utils/object-utils';
|
||||
import {StateFacade} from '@base/facades/state';
|
||||
|
||||
export interface AppState {
|
||||
loading: boolean;
|
||||
module: string;
|
||||
view: string;
|
||||
loaded: boolean;
|
||||
}
|
||||
|
||||
const initialState: AppState = {
|
||||
loading: false,
|
||||
module: null,
|
||||
view: null,
|
||||
loaded: false
|
||||
};
|
||||
|
||||
|
@ -25,19 +29,29 @@ export class AppStateFacade implements StateFacade {
|
|||
protected state$ = this.store.asObservable();
|
||||
protected loadingQueue = {};
|
||||
|
||||
/**
|
||||
* Public long-lived observable streams
|
||||
*/
|
||||
|
||||
loading$ = this.state$.pipe(map(state => state.loading), distinctUntilChanged());
|
||||
module$ = this.state$.pipe(map(state => state.module), distinctUntilChanged());
|
||||
view$ = this.state$.pipe(map(state => state.view), distinctUntilChanged());
|
||||
|
||||
/**
|
||||
* ViewModel that resolves once all the data is ready (or updated)...
|
||||
*/
|
||||
vm$: Observable<AppState> = combineLatest([this.loading$]).pipe(
|
||||
map(([loading]) => ({loading, loaded: internalState.loaded}))
|
||||
vm$: Observable<AppState> = combineLatest([this.loading$, this.module$, this.view$]).pipe(
|
||||
map(([loading, module, view]) => ({loading, module, view, loaded: internalState.loaded}))
|
||||
);
|
||||
|
||||
constructor() {
|
||||
this.updateState({...internalState, loading: false});
|
||||
}
|
||||
|
||||
/**
|
||||
* Public Api
|
||||
*/
|
||||
|
||||
/**
|
||||
* Clear state
|
||||
*/
|
||||
|
@ -85,6 +99,24 @@ export class AppStateFacade implements StateFacade {
|
|||
this.updateState({...internalState, loaded});
|
||||
}
|
||||
|
||||
/**
|
||||
* Set current module
|
||||
*
|
||||
* @param {string} module to set as current module
|
||||
*/
|
||||
public setModule(module: string): void {
|
||||
this.updateState({...internalState, module});
|
||||
}
|
||||
|
||||
/**
|
||||
* Set current View
|
||||
*
|
||||
* @param {string} view to set as current view
|
||||
*/
|
||||
public setView(view: string): void {
|
||||
this.updateState({...internalState, view});
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal API
|
||||
*/
|
||||
|
@ -121,9 +153,9 @@ export class AppStateFacade implements StateFacade {
|
|||
/**
|
||||
* Update the state
|
||||
*
|
||||
* @param state
|
||||
* @param {{}} state app state
|
||||
*/
|
||||
protected updateState(state: AppState) {
|
||||
protected updateState(state: AppState): void {
|
||||
this.store.next(internalState = state);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,6 +14,7 @@ export const languageMockData = {
|
|||
LBL_SEND: 'Send',
|
||||
LBL_LOGOUT: 'Logout',
|
||||
LBL_TOUR_NEXT: 'Next',
|
||||
LBL_LAST_VIEWED: 'Recently Viewed'
|
||||
},
|
||||
appListStrings: {
|
||||
language_pack_name: 'US English',
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import {Injectable} from '@angular/core';
|
||||
|
||||
import {BehaviorSubject, forkJoin, Observable} from 'rxjs';
|
||||
import {BehaviorSubject, combineLatest, forkJoin, Observable} from 'rxjs';
|
||||
import {distinctUntilChanged, first, map, shareReplay, tap} from 'rxjs/operators';
|
||||
import {RecordGQL} from '@services/api/graphql-api/api.record.get';
|
||||
import {AppStateFacade} from '@base/facades/app-state/app-state.facade';
|
||||
|
@ -28,6 +28,13 @@ export interface LanguageState {
|
|||
hasChanged: boolean;
|
||||
}
|
||||
|
||||
export interface LanguageStrings {
|
||||
appStrings: LanguageStringMap;
|
||||
appListStrings: LanguageListStringMap;
|
||||
modStrings: LanguageListStringMap;
|
||||
languageKey: string;
|
||||
}
|
||||
|
||||
export interface LanguageCache {
|
||||
[key: string]: {
|
||||
[key: string]: Observable<any>;
|
||||
|
@ -105,6 +112,27 @@ export class LanguageFacade implements StateFacade {
|
|||
modStrings$ = this.state$.pipe(map(state => state.modStrings), distinctUntilChanged());
|
||||
languageKey$ = this.state$.pipe(map(state => state.languageKey), distinctUntilChanged());
|
||||
|
||||
/**
|
||||
* ViewModel that resolves once all the data is ready (or updated)...
|
||||
*/
|
||||
vm$: Observable<LanguageStrings> = combineLatest(
|
||||
[
|
||||
this.appStrings$,
|
||||
this.appListStrings$,
|
||||
this.modStrings$,
|
||||
this.languageKey$
|
||||
])
|
||||
.pipe(
|
||||
map((
|
||||
[
|
||||
appStrings,
|
||||
appListStrings,
|
||||
modStrings,
|
||||
languageKey
|
||||
]) => ({appStrings, appListStrings, modStrings, languageKey})
|
||||
)
|
||||
);
|
||||
|
||||
constructor(private recordGQL: RecordGQL, private appStateFacade: AppStateFacade) {
|
||||
}
|
||||
|
||||
|
@ -124,7 +152,7 @@ export class LanguageFacade implements StateFacade {
|
|||
/**
|
||||
* Update the language strings toe the given language
|
||||
*
|
||||
* @param languageKey
|
||||
* @param {string} languageKey language key
|
||||
*/
|
||||
public changeLanguage(languageKey: string): void {
|
||||
const types = [];
|
||||
|
@ -143,7 +171,8 @@ export class LanguageFacade implements StateFacade {
|
|||
/**
|
||||
* Get AppStrings label by key
|
||||
*
|
||||
* @param labelKey
|
||||
* @param {string} labelKey to fetch
|
||||
* @returns {string} label
|
||||
*/
|
||||
public getAppString(labelKey: string): string {
|
||||
|
||||
|
@ -156,7 +185,8 @@ export class LanguageFacade implements StateFacade {
|
|||
/**
|
||||
* Get AppListStrings label by key
|
||||
*
|
||||
* @param labelKey
|
||||
* @param {string} labelKey to fetch
|
||||
* @returns {string|{}} app strings
|
||||
*/
|
||||
public getAppListString(labelKey: string): string | LanguageStringMap {
|
||||
|
||||
|
@ -170,7 +200,8 @@ export class LanguageFacade implements StateFacade {
|
|||
/**
|
||||
* Get ModStrings label by key
|
||||
*
|
||||
* @param labelKey
|
||||
* @param {string} labelKey to fetch
|
||||
* @returns {string|{}} mod strings
|
||||
*/
|
||||
public getModString(labelKey: string): string | LanguageStringMap {
|
||||
|
||||
|
@ -184,7 +215,7 @@ export class LanguageFacade implements StateFacade {
|
|||
/**
|
||||
* Get all available string types
|
||||
*
|
||||
* @returns Observable
|
||||
* @returns {string[]} string types
|
||||
*/
|
||||
public getAvailableStringsTypes(): string[] {
|
||||
return Object.keys(this.config);
|
||||
|
@ -193,7 +224,7 @@ export class LanguageFacade implements StateFacade {
|
|||
/**
|
||||
* Returns whether the language has changed manually
|
||||
*
|
||||
* @returns bool
|
||||
* @returns {boolean} has changed
|
||||
*/
|
||||
public hasLanguageChanged(): boolean {
|
||||
return internalState.hasChanged;
|
||||
|
@ -202,7 +233,7 @@ export class LanguageFacade implements StateFacade {
|
|||
/**
|
||||
* Returns the currently active language
|
||||
*
|
||||
* @returns string
|
||||
* @returns {string} current language key
|
||||
*/
|
||||
public getCurrentLanguage(): string {
|
||||
return internalState.languageKey;
|
||||
|
@ -213,9 +244,9 @@ export class LanguageFacade implements StateFacade {
|
|||
* Initial Language Strings Load for given language and types if not cached and update state.
|
||||
* Returns observable to be used in resolver if needed
|
||||
*
|
||||
* @param languageKey
|
||||
* @param types
|
||||
* @returns Observable
|
||||
* @param {string} languageKey to load
|
||||
* @param {string[]} types to load
|
||||
* @returns {{}} Observable
|
||||
*/
|
||||
public load(languageKey: string, types: string[]): Observable<{}> {
|
||||
|
||||
|
@ -247,7 +278,7 @@ export class LanguageFacade implements StateFacade {
|
|||
/**
|
||||
* Update internal state cache and emit from store...
|
||||
*
|
||||
* @param state
|
||||
* @param {{}} state to set
|
||||
*/
|
||||
protected updateState(state: LanguageState): void {
|
||||
this.store.next(internalState = state);
|
||||
|
@ -256,9 +287,9 @@ export class LanguageFacade implements StateFacade {
|
|||
/**
|
||||
* Get given $type of strings Observable from cache or call the backend
|
||||
*
|
||||
* @param language
|
||||
* @param type
|
||||
* @returns Observable<any>
|
||||
* @param {string} language to load
|
||||
* @param {string} type load
|
||||
* @returns {{}} Observable<any>
|
||||
*/
|
||||
protected getStrings(language: string, type: string): Observable<{}> {
|
||||
|
||||
|
@ -279,8 +310,8 @@ export class LanguageFacade implements StateFacade {
|
|||
/**
|
||||
* Fetch the App strings from the backend
|
||||
*
|
||||
* @param language
|
||||
* @returns Observable<{}>
|
||||
* @param {string} language to fetch
|
||||
* @returns {{}} Observable<{}>
|
||||
*/
|
||||
protected fetchAppStrings(language: string): Observable<{}> {
|
||||
const resourceName = this.config.appStrings.resourceName;
|
||||
|
@ -302,8 +333,8 @@ export class LanguageFacade implements StateFacade {
|
|||
/**
|
||||
* Fetch the App list strings from the backend
|
||||
*
|
||||
* @param language
|
||||
* @returns Observable<{}>
|
||||
* @param {string} language to fetch
|
||||
* @returns {{}} Observable<{}>
|
||||
*/
|
||||
protected fetchAppListStrings(language: string): Observable<{}> {
|
||||
const resourceName = this.config.appListStrings.resourceName;
|
||||
|
@ -326,8 +357,8 @@ export class LanguageFacade implements StateFacade {
|
|||
/**
|
||||
* Fetch the Mod strings from the backend
|
||||
*
|
||||
* @param language
|
||||
* @returns Observable<{}>
|
||||
* @param {string} language to fetch
|
||||
* @returns {{}} Observable<{}>
|
||||
*/
|
||||
protected fetchModStrings(language: string): Observable<{}> {
|
||||
const resourceName = this.config.modStrings.resourceName;
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import {Injectable} from '@angular/core';
|
||||
import {BehaviorSubject, Observable} from 'rxjs';
|
||||
import {BehaviorSubject, combineLatest, Observable} from 'rxjs';
|
||||
import {map, distinctUntilChanged, tap, shareReplay} from 'rxjs/operators';
|
||||
|
||||
import {RecordGQL} from '@services/api/graphql-api/api.record.get';
|
||||
|
@ -11,6 +11,7 @@ export interface Navigation {
|
|||
groupedTabs: GroupedTab[];
|
||||
modules: NavbarModuleMap;
|
||||
userActionMenu: UserActionMenu[];
|
||||
maxTabs: number;
|
||||
}
|
||||
|
||||
export interface NavbarModuleMap {
|
||||
|
@ -51,7 +52,8 @@ const initialState: Navigation = {
|
|||
tabs: [],
|
||||
groupedTabs: [],
|
||||
modules: {},
|
||||
userActionMenu: []
|
||||
userActionMenu: [],
|
||||
maxTabs: 0
|
||||
};
|
||||
|
||||
let internalState: Navigation = deepClone(initialState);
|
||||
|
@ -71,7 +73,8 @@ export class NavigationFacade implements StateFacade {
|
|||
'tabs',
|
||||
'groupedTabs',
|
||||
'modules',
|
||||
'userActionMenu'
|
||||
'userActionMenu',
|
||||
'maxTabs'
|
||||
]
|
||||
};
|
||||
|
||||
|
@ -82,6 +85,31 @@ export class NavigationFacade implements StateFacade {
|
|||
groupedTabs$ = this.state$.pipe(map(state => state.groupedTabs), distinctUntilChanged());
|
||||
modules$ = this.state$.pipe(map(state => state.modules), distinctUntilChanged());
|
||||
userActionMenu$ = this.state$.pipe(map(state => state.userActionMenu), distinctUntilChanged());
|
||||
maxTabs$ = this.state$.pipe(map(state => state.maxTabs), distinctUntilChanged());
|
||||
|
||||
|
||||
/**
|
||||
* ViewModel that resolves once all the data is ready (or updated)...
|
||||
*/
|
||||
vm$: Observable<Navigation> = combineLatest(
|
||||
[
|
||||
this.tabs$,
|
||||
this.groupedTabs$,
|
||||
this.modules$,
|
||||
this.userActionMenu$,
|
||||
this.maxTabs$
|
||||
])
|
||||
.pipe(
|
||||
map((
|
||||
[
|
||||
tabs,
|
||||
groupedTabs,
|
||||
modules,
|
||||
userActionMenu,
|
||||
maxTabs
|
||||
]) => ({tabs, groupedTabs, modules, userActionMenu, maxTabs})
|
||||
)
|
||||
);
|
||||
|
||||
constructor(private recordGQL: RecordGQL) {
|
||||
}
|
||||
|
@ -104,7 +132,7 @@ export class NavigationFacade implements StateFacade {
|
|||
* Initial Navigation load if not cached and update state.
|
||||
* Returns observable to be used in resolver if needed
|
||||
*
|
||||
* @returns Observable<any>
|
||||
* @returns {{}} Observable<any>
|
||||
*/
|
||||
public load(): Observable<any> {
|
||||
|
||||
|
@ -115,7 +143,8 @@ export class NavigationFacade implements StateFacade {
|
|||
tabs: navigation.tabs,
|
||||
groupedTabs: navigation.groupedTabs,
|
||||
userActionMenu: navigation.userActionMenu,
|
||||
modules: navigation.modules
|
||||
modules: navigation.modules,
|
||||
maxTabs: navigation.maxTabs
|
||||
});
|
||||
})
|
||||
);
|
||||
|
@ -128,16 +157,16 @@ export class NavigationFacade implements StateFacade {
|
|||
/**
|
||||
* Update the state
|
||||
*
|
||||
* @param state
|
||||
* @param {{}} state to set
|
||||
*/
|
||||
protected updateState(state: Navigation) {
|
||||
protected updateState(state: Navigation): void {
|
||||
this.store.next(internalState = state);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Navigation cached Observable or call the backend
|
||||
*
|
||||
* @returns Observable<any>
|
||||
* @returns {{}} Observable<any>
|
||||
*/
|
||||
protected getNavigation(): Observable<any> {
|
||||
|
||||
|
@ -155,8 +184,8 @@ export class NavigationFacade implements StateFacade {
|
|||
/**
|
||||
* Fetch the Navigation from the backend
|
||||
*
|
||||
* @param userId
|
||||
* @returns Observable<any>
|
||||
* @param {string} userId to use
|
||||
* @returns {{}} Observable<any>
|
||||
*/
|
||||
protected fetch(userId: string): Observable<any> {
|
||||
|
||||
|
@ -171,7 +200,8 @@ export class NavigationFacade implements StateFacade {
|
|||
tabs: data.navbar.tabs,
|
||||
groupedTabs: data.navbar.groupedTabs,
|
||||
userActionMenu: data.navbar.userActionMenu,
|
||||
modules: data.navbar.modules
|
||||
modules: data.navbar.modules,
|
||||
maxTabs: data.navbar.maxTabs
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
@ -95,6 +95,16 @@ export const themeImagesMockData = {
|
|||
path: 'core/app/themes/suite8/images/paginate_last.svg',
|
||||
name: 'paginate_last',
|
||||
type: 'svg'
|
||||
},
|
||||
home: {
|
||||
path: 'core/app/themes/suite8/images/home.svg',
|
||||
name: 'home',
|
||||
type: 'svg'
|
||||
},
|
||||
view: {
|
||||
path: 'core/app/themes/suite8/images/view.svg',
|
||||
name: 'view',
|
||||
type: 'svg'
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import {Injectable} from '@angular/core';
|
||||
import {ActivatedRouteSnapshot} from '@angular/router';
|
||||
import {map} from 'rxjs/operators';
|
||||
import {RouteConverter} from '@services/navigation/route-converter/route-converter.service';
|
||||
import {BaseMetadataResolver} from '@services/metadata/base-metadata.resolver';
|
||||
import {SystemConfigFacade} from '@base/facades/system-config/system-config.facade';
|
||||
|
@ -7,11 +8,13 @@ import {LanguageFacade} from '@base/facades/language/language.facade';
|
|||
import {NavigationFacade} from '@base/facades/navigation/navigation.facade';
|
||||
import {UserPreferenceFacade} from '@base/facades/user-preference/user-preference.facade';
|
||||
import {ThemeImagesFacade} from '@base/facades/theme-images/theme-images.facade';
|
||||
import {map} from 'rxjs/operators';
|
||||
import {ModuleNameMapper} from '@services/navigation/module-name-mapper/module-name-mapper.service';
|
||||
import {ActionNameMapper} from '@services/navigation/action-name-mapper/action-name-mapper.service';
|
||||
import {BaseModuleResolver} from '@services/metadata/base-module.resolver';
|
||||
import {AppStateFacade} from '@base/facades/app-state/app-state.facade';
|
||||
|
||||
@Injectable({providedIn: 'root'})
|
||||
export class ClassicViewResolver extends BaseMetadataResolver {
|
||||
export class ClassicViewResolver extends BaseModuleResolver {
|
||||
|
||||
constructor(
|
||||
protected systemConfigFacade: SystemConfigFacade,
|
||||
|
@ -19,8 +22,10 @@ export class ClassicViewResolver extends BaseMetadataResolver {
|
|||
protected navigationFacade: NavigationFacade,
|
||||
protected userPreferenceFacade: UserPreferenceFacade,
|
||||
protected themeImagesFacade: ThemeImagesFacade,
|
||||
protected moduleNameMapper: ModuleNameMapper,
|
||||
protected actionNameMapper: ActionNameMapper,
|
||||
protected appStateFacade: AppStateFacade,
|
||||
protected routeConverter: RouteConverter,
|
||||
protected appState: AppStateFacade
|
||||
) {
|
||||
super(
|
||||
systemConfigFacade,
|
||||
|
@ -28,7 +33,9 @@ export class ClassicViewResolver extends BaseMetadataResolver {
|
|||
navigationFacade,
|
||||
userPreferenceFacade,
|
||||
themeImagesFacade,
|
||||
appState
|
||||
moduleNameMapper,
|
||||
actionNameMapper,
|
||||
appStateFacade
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -40,7 +40,7 @@ export class BaseMetadataResolver implements Resolve<any> {
|
|||
|
||||
configs$ = configs$.pipe(
|
||||
map(
|
||||
configs => {
|
||||
(configs: any) => {
|
||||
|
||||
let language = configs.default_language.value;
|
||||
|
||||
|
|
82
core/app/src/services/metadata/base-module.resolver.ts
Normal file
82
core/app/src/services/metadata/base-module.resolver.ts
Normal file
|
@ -0,0 +1,82 @@
|
|||
import {Injectable} from '@angular/core';
|
||||
import {ActivatedRouteSnapshot} from '@angular/router';
|
||||
import {tap} from 'rxjs/operators';
|
||||
import {BaseMetadataResolver} from '@services/metadata/base-metadata.resolver';
|
||||
import {ModuleNameMapper} from '@services/navigation/module-name-mapper/module-name-mapper.service';
|
||||
import {ActionNameMapper} from '@services/navigation/action-name-mapper/action-name-mapper.service';
|
||||
import {SystemConfigFacade} from '@base/facades/system-config/system-config.facade';
|
||||
import {LanguageFacade} from '@base/facades/language/language.facade';
|
||||
import {NavigationFacade} from '@base/facades/navigation/navigation.facade';
|
||||
import {UserPreferenceFacade} from '@base/facades/user-preference/user-preference.facade';
|
||||
import {ThemeImagesFacade} from '@base/facades/theme-images/theme-images.facade';
|
||||
import {AppStateFacade} from '@base/facades/app-state/app-state.facade';
|
||||
|
||||
@Injectable({providedIn: 'root'})
|
||||
export class BaseModuleResolver extends BaseMetadataResolver {
|
||||
|
||||
constructor(
|
||||
protected systemConfigFacade: SystemConfigFacade,
|
||||
protected languageFacade: LanguageFacade,
|
||||
protected navigationFacade: NavigationFacade,
|
||||
protected userPreferenceFacade: UserPreferenceFacade,
|
||||
protected themeImagesFacade: ThemeImagesFacade,
|
||||
protected moduleNameMapper: ModuleNameMapper,
|
||||
protected actionNameMapper: ActionNameMapper,
|
||||
protected appStateFacade: AppStateFacade,
|
||||
) {
|
||||
super(
|
||||
systemConfigFacade,
|
||||
languageFacade,
|
||||
navigationFacade,
|
||||
userPreferenceFacade,
|
||||
themeImagesFacade,
|
||||
appStateFacade
|
||||
);
|
||||
}
|
||||
|
||||
resolve(route: ActivatedRouteSnapshot): any {
|
||||
|
||||
return super.resolve(route).pipe(
|
||||
tap(() => {
|
||||
if (route.params.module) {
|
||||
const module = this.calculateActiveModule(route);
|
||||
|
||||
this.appStateFacade.setModule(module);
|
||||
}
|
||||
if (route.params.action) {
|
||||
this.appStateFacade.setView(route.params.action);
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the active module
|
||||
*
|
||||
* @param {{}} route active
|
||||
* @returns {string} active module
|
||||
*/
|
||||
protected calculateActiveModule(route: ActivatedRouteSnapshot): string {
|
||||
|
||||
let module = route.params.module;
|
||||
const parentModuleParam = this.getParentModuleMap()[module] || '';
|
||||
const parentModule = route.queryParams[parentModuleParam] || '';
|
||||
|
||||
if (parentModule) {
|
||||
module = this.moduleNameMapper.toFrontend(parentModule);
|
||||
}
|
||||
return module;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Parent Module Map
|
||||
*
|
||||
* @returns {{}} parent module map
|
||||
*/
|
||||
protected getParentModuleMap(): { [key: string]: string } {
|
||||
return {
|
||||
'merge-records': 'return_module',
|
||||
import: 'import_module'
|
||||
};
|
||||
}
|
||||
}
|
|
@ -48,6 +48,10 @@ a.home-nav-link {
|
|||
padding: 1em 0.5em 1em 0.5em;
|
||||
}
|
||||
|
||||
.nav-item.active .home-nav-link svg {
|
||||
fill: $salmon-pink;
|
||||
}
|
||||
|
||||
.recent-link scrm-svg-icon-ui > svg {
|
||||
margin: 0.3em 0.8em 0.8em 0;
|
||||
}
|
||||
|
@ -70,6 +74,14 @@ a.home-nav-link {
|
|||
color: $off-white;
|
||||
}
|
||||
|
||||
.nav-item.active .nav-link-nongrouped,
|
||||
.nav-item.active .home-nav-link {
|
||||
color: $salmon-pink;
|
||||
border-top: 0.2em solid $salmon-pink;
|
||||
padding: 0.8em 0.5em 1em 0.5em;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.global-link-item:hover {
|
||||
color: $salmon-pink;
|
||||
}
|
||||
|
|
|
@ -19,7 +19,8 @@
|
|||
"@base/*": ["./src/*"],
|
||||
"@views/*": ["./views/*"],
|
||||
"@services/*": ["./src/services/*"],
|
||||
"@components/*": ["./src/components/*"]
|
||||
"@components/*": ["./src/components/*"],
|
||||
"@store/*": ["./src/facades/*"]
|
||||
},
|
||||
"lib": [
|
||||
"es2018",
|
||||
|
|
|
@ -7,6 +7,7 @@ namespace SuiteCRM\Core\Legacy;
|
|||
use ApiPlatform\Core\Exception\ItemNotFoundException;
|
||||
use App\Entity\ModStrings;
|
||||
use App\Service\ModuleNameMapperInterface;
|
||||
use App\Service\ModuleRegistryInterface;
|
||||
|
||||
class ModStringsHandler extends LegacyHandler
|
||||
{
|
||||
|
@ -18,6 +19,11 @@ class ModStringsHandler extends LegacyHandler
|
|||
*/
|
||||
private $moduleNameMapper;
|
||||
|
||||
/**
|
||||
* @var ModuleRegistryInterface
|
||||
*/
|
||||
private $moduleRegistry;
|
||||
|
||||
/**
|
||||
* SystemConfigHandler constructor.
|
||||
* @param string $projectDir
|
||||
|
@ -26,6 +32,7 @@ class ModStringsHandler extends LegacyHandler
|
|||
* @param string $defaultSessionName
|
||||
* @param LegacyScopeState $legacyScopeState
|
||||
* @param ModuleNameMapperInterface $moduleNameMapper
|
||||
* @param ModuleRegistryInterface $moduleRegistry
|
||||
*/
|
||||
public function __construct(
|
||||
string $projectDir,
|
||||
|
@ -33,10 +40,12 @@ class ModStringsHandler extends LegacyHandler
|
|||
string $legacySessionName,
|
||||
string $defaultSessionName,
|
||||
LegacyScopeState $legacyScopeState,
|
||||
ModuleNameMapperInterface $moduleNameMapper
|
||||
ModuleNameMapperInterface $moduleNameMapper,
|
||||
ModuleRegistryInterface $moduleRegistry
|
||||
) {
|
||||
parent::__construct($projectDir, $legacyDir, $legacySessionName, $defaultSessionName, $legacyScopeState);
|
||||
$this->moduleNameMapper = $moduleNameMapper;
|
||||
$this->moduleRegistry = $moduleRegistry;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -66,10 +75,11 @@ class ModStringsHandler extends LegacyHandler
|
|||
throw new ItemNotFoundException(self::MSG_LANGUAGE_NOT_FOUND . "'$language'");
|
||||
}
|
||||
|
||||
global $moduleList;
|
||||
$modules = $this->moduleRegistry->getUserAccessibleModules();
|
||||
|
||||
|
||||
$allModStringsArray = [];
|
||||
foreach ($moduleList as $module) {
|
||||
foreach ($modules as $module) {
|
||||
$frontendName = $this->moduleNameMapper->toFrontEnd($module);
|
||||
$allModStringsArray[$frontendName] = return_module_language($language, $module);
|
||||
}
|
||||
|
|
131
core/legacy/ModuleRegistryHandler.php
Normal file
131
core/legacy/ModuleRegistryHandler.php
Normal file
|
@ -0,0 +1,131 @@
|
|||
<?php
|
||||
|
||||
namespace SuiteCRM\Core\Legacy;
|
||||
|
||||
use ACLAction;
|
||||
use ACLController;
|
||||
use App\Service\ModuleRegistryInterface;
|
||||
|
||||
class ModuleRegistryHandler extends LegacyHandler implements ModuleRegistryInterface
|
||||
{
|
||||
public const HANDLER_KEY = 'module-registry';
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private $frontendExcludedModules;
|
||||
|
||||
/**
|
||||
* SystemConfigHandler constructor.
|
||||
* @param string $projectDir
|
||||
* @param string $legacyDir
|
||||
* @param string $legacySessionName
|
||||
* @param string $defaultSessionName
|
||||
* @param LegacyScopeState $legacyScopeState
|
||||
* @param array $frontendExcludedModules
|
||||
*/
|
||||
public function __construct(
|
||||
string $projectDir,
|
||||
string $legacyDir,
|
||||
string $legacySessionName,
|
||||
string $defaultSessionName,
|
||||
LegacyScopeState $legacyScopeState,
|
||||
array $frontendExcludedModules
|
||||
) {
|
||||
parent::__construct($projectDir, $legacyDir, $legacySessionName, $defaultSessionName, $legacyScopeState);
|
||||
$this->frontendExcludedModules = $frontendExcludedModules;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function getHandlerKey(): string
|
||||
{
|
||||
return self::HANDLER_KEY;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get list of modules the user has access to
|
||||
* @return array
|
||||
* Based on @see {soap/SoapHelperFunctions.php::get_user_module_list}
|
||||
*/
|
||||
public function getUserAccessibleModules(): array
|
||||
{
|
||||
$this->init();
|
||||
|
||||
global $modInvisList;
|
||||
|
||||
$modules = $this->getFilterVisibleModules();
|
||||
|
||||
if (empty($modules)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
foreach ($modInvisList as $invis) {
|
||||
$modules[$invis] = '';
|
||||
}
|
||||
|
||||
$modules = $this->applyUserActionFilter($modules);
|
||||
|
||||
foreach ($this->frontendExcludedModules as $excluded){
|
||||
unset($modules[$excluded]);
|
||||
}
|
||||
|
||||
if (empty($modules)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$this->close();
|
||||
|
||||
return array_keys($modules);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get of list of modules. Apply acl filter
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function getFilterVisibleModules(): array
|
||||
{
|
||||
global $current_user;
|
||||
|
||||
$modules = query_module_access_list($current_user);
|
||||
|
||||
if (empty($modules)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
ACLController:: filterModuleList($modules, false);
|
||||
|
||||
return $modules;
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply User action filter on current module list
|
||||
*
|
||||
* @param array $modules
|
||||
* @return array
|
||||
*/
|
||||
protected function applyUserActionFilter(array &$modules): array
|
||||
{
|
||||
global $current_user;
|
||||
|
||||
$actions = ACLAction::getUserActions($current_user->id, true);
|
||||
foreach ($actions as $key => $value) {
|
||||
if (!isset($value['module'])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($value['module']['access']['aclaccess'] < ACL_ALLOW_ENABLED) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($value['module']['access']['aclaccess'] === ACL_ALLOW_DISABLED) {
|
||||
unset($modules[$key]);
|
||||
}
|
||||
}
|
||||
|
||||
return $modules; // foreach
|
||||
}
|
||||
}
|
|
@ -4,6 +4,7 @@ namespace SuiteCRM\Core\Legacy;
|
|||
|
||||
use App\Entity\Navbar;
|
||||
use App\Service\ModuleNameMapperInterface;
|
||||
use App\Service\ModuleRegistryInterface;
|
||||
use App\Service\NavigationProviderInterface;
|
||||
use App\Service\RouteConverterInterface;
|
||||
use GroupedTabStructure;
|
||||
|
@ -31,6 +32,11 @@ class NavbarHandler extends LegacyHandler implements NavigationProviderInterface
|
|||
*/
|
||||
private $menuItemMap;
|
||||
|
||||
/**
|
||||
* @var ModuleRegistryInterface
|
||||
*/
|
||||
private $moduleRegistry;
|
||||
|
||||
/**
|
||||
* SystemConfigHandler constructor.
|
||||
* @param string $projectDir
|
||||
|
@ -41,6 +47,7 @@ class NavbarHandler extends LegacyHandler implements NavigationProviderInterface
|
|||
* @param array $menuItemMap
|
||||
* @param ModuleNameMapperInterface $moduleNameMapper
|
||||
* @param RouteConverterInterface $routeConverter
|
||||
* @param ModuleRegistryInterface $moduleRegistry
|
||||
*/
|
||||
public function __construct(
|
||||
string $projectDir,
|
||||
|
@ -50,12 +57,14 @@ class NavbarHandler extends LegacyHandler implements NavigationProviderInterface
|
|||
LegacyScopeState $legacyScopeState,
|
||||
array $menuItemMap,
|
||||
ModuleNameMapperInterface $moduleNameMapper,
|
||||
RouteConverterInterface $routeConverter
|
||||
RouteConverterInterface $routeConverter,
|
||||
ModuleRegistryInterface $moduleRegistry
|
||||
) {
|
||||
parent::__construct($projectDir, $legacyDir, $legacySessionName, $defaultSessionName, $legacyScopeState);
|
||||
$this->moduleNameMapper = $moduleNameMapper;
|
||||
$this->routeConverter = $routeConverter;
|
||||
$this->menuItemMap = $menuItemMap;
|
||||
$this->moduleRegistry = $moduleRegistry;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -92,6 +101,7 @@ class NavbarHandler extends LegacyHandler implements NavigationProviderInterface
|
|||
$navbar->modules = $this->buildModuleInfo($sugarView, $accessibleModulesNameMap);
|
||||
|
||||
$navbar->userActionMenu = $this->fetchUserActionMenu();
|
||||
$navbar->maxTabs = $this->getMaxTabs();
|
||||
|
||||
$this->close();
|
||||
|
||||
|
@ -116,10 +126,7 @@ class NavbarHandler extends LegacyHandler implements NavigationProviderInterface
|
|||
*/
|
||||
protected function getAccessibleModulesList(): array
|
||||
{
|
||||
/* @noinspection PhpIncludeInspection */
|
||||
require_once 'modules/MySettings/TabController.php';
|
||||
|
||||
return (new TabController())->get_user_tabs($GLOBALS['current_user']);
|
||||
return $this->moduleRegistry->getUserAccessibleModules();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -148,8 +155,6 @@ class NavbarHandler extends LegacyHandler implements NavigationProviderInterface
|
|||
}
|
||||
}
|
||||
|
||||
sort($submoduleArray);
|
||||
|
||||
$output[] = [
|
||||
|
||||
'name' => $mainTab,
|
||||
|
@ -232,6 +237,30 @@ class NavbarHandler extends LegacyHandler implements NavigationProviderInterface
|
|||
return array_values($userActionMenu);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get max number of tabs
|
||||
* @return int
|
||||
* Based on @link SugarView
|
||||
*/
|
||||
protected function getMaxTabs(): int
|
||||
{
|
||||
global $current_user;
|
||||
|
||||
$maxTabs = $current_user->getPreference('max_tabs');
|
||||
|
||||
// If the max_tabs isn't set incorrectly, set it within the range, to the default max sub tabs size
|
||||
if (!isset($maxTabs) || $maxTabs <= 0 || $maxTabs > 10) {
|
||||
// We have a default value. Use it
|
||||
if (isset($GLOBALS['sugar_config']['default_max_tabs'])) {
|
||||
$maxTabs = $GLOBALS['sugar_config']['default_max_tabs'];
|
||||
} else {
|
||||
$maxTabs = 8;
|
||||
}
|
||||
}
|
||||
|
||||
return $maxTabs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get global control links from legacy
|
||||
* @return array
|
||||
|
|
|
@ -48,4 +48,10 @@ final class Navbar
|
|||
* @ApiProperty
|
||||
*/
|
||||
public $modules;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
* @ApiProperty
|
||||
*/
|
||||
public $maxTabs;
|
||||
}
|
||||
|
|
12
core/src/Service/ModuleRegistryInterface.php
Normal file
12
core/src/Service/ModuleRegistryInterface.php
Normal file
|
@ -0,0 +1,12 @@
|
|||
<?php
|
||||
|
||||
namespace App\Service;
|
||||
|
||||
interface ModuleRegistryInterface
|
||||
{
|
||||
/**
|
||||
* Get list of modules the user has access to
|
||||
* @return array list of module names
|
||||
*/
|
||||
public function getUserAccessibleModules(): array;
|
||||
}
|
|
@ -6,6 +6,7 @@ use Codeception\Test\Unit;
|
|||
use SuiteCRM\Core\Legacy\LegacyScopeState;
|
||||
use SuiteCRM\Core\Legacy\ModStringsHandler;
|
||||
use SuiteCRM\Core\Legacy\ModuleNameMapperHandler;
|
||||
use SuiteCRM\Core\Legacy\ModuleRegistryHandler;
|
||||
|
||||
class ModStringsHandlerTest extends Unit
|
||||
{
|
||||
|
@ -36,13 +37,31 @@ class ModStringsHandlerTest extends Unit
|
|||
$legacyScope
|
||||
);
|
||||
|
||||
$excludedModules = [
|
||||
'EmailText',
|
||||
'TeamMemberships',
|
||||
'TeamSets',
|
||||
'TeamSetModule'
|
||||
];
|
||||
|
||||
$moduleRegistry = new ModuleRegistryHandler(
|
||||
$projectDir,
|
||||
$legacyDir,
|
||||
$legacySessionName,
|
||||
$defaultSessionName,
|
||||
$legacyScope,
|
||||
$excludedModules
|
||||
);
|
||||
|
||||
|
||||
$this->handler = new ModStringsHandler(
|
||||
$projectDir,
|
||||
$legacyDir,
|
||||
$legacySessionName,
|
||||
$defaultSessionName,
|
||||
$legacyScope,
|
||||
$moduleNameMapper
|
||||
$moduleNameMapper,
|
||||
$moduleRegistry
|
||||
);
|
||||
}
|
||||
|
||||
|
|
59
tests/unit/core/legacy/ModuleRegistryHandlerTest.php
Normal file
59
tests/unit/core/legacy/ModuleRegistryHandlerTest.php
Normal file
|
@ -0,0 +1,59 @@
|
|||
<?php namespace App\Tests;
|
||||
|
||||
use Codeception\Test\Unit;
|
||||
use SuiteCRM\Core\Legacy\LegacyScopeState;
|
||||
use SuiteCRM\Core\Legacy\ModuleRegistryHandler;
|
||||
|
||||
class ModuleRegistryHandlerTest extends Unit
|
||||
{
|
||||
/**
|
||||
* @var UnitTester
|
||||
*/
|
||||
protected $tester;
|
||||
|
||||
/**
|
||||
* @var ModuleRegistryHandler
|
||||
*/
|
||||
protected $handler;
|
||||
|
||||
protected function _before(): void
|
||||
{
|
||||
$projectDir = codecept_root_dir();
|
||||
$legacyDir = $projectDir . '/legacy';
|
||||
$legacySessionName = 'LEGACYSESSID';
|
||||
$defaultSessionName = 'PHPSESSID';
|
||||
|
||||
$legacyScope = new LegacyScopeState();
|
||||
|
||||
$excludedModules = [
|
||||
'EmailText',
|
||||
'TeamMemberships',
|
||||
'TeamSets',
|
||||
'TeamSetModule'
|
||||
];
|
||||
|
||||
$this->handler = new ModuleRegistryHandler(
|
||||
$projectDir,
|
||||
$legacyDir,
|
||||
$legacySessionName,
|
||||
$defaultSessionName,
|
||||
$legacyScope,
|
||||
$excludedModules
|
||||
);
|
||||
}
|
||||
|
||||
// tests
|
||||
|
||||
/**
|
||||
* Test accessible modules retrieval
|
||||
*/
|
||||
public function testGetAccessibleModules(): void
|
||||
{
|
||||
|
||||
$modules = $this->handler->getUserAccessibleModules();
|
||||
|
||||
static::assertContainsEquals('Accounts', $modules);
|
||||
static::assertContainsEquals('Alert', $modules);
|
||||
static::assertContainsEquals('EmailMan', $modules);
|
||||
}
|
||||
}
|
|
@ -8,6 +8,7 @@ use Codeception\Test\Unit;
|
|||
use SuiteCRM\Core\Legacy\ActionNameMapperHandler;
|
||||
use SuiteCRM\Core\Legacy\LegacyScopeState;
|
||||
use SuiteCRM\Core\Legacy\ModuleNameMapperHandler;
|
||||
use SuiteCRM\Core\Legacy\ModuleRegistryHandler;
|
||||
use SuiteCRM\Core\Legacy\NavbarHandler;
|
||||
use SuiteCRM\Core\Legacy\RouteConverterHandler;
|
||||
|
||||
|
@ -169,6 +170,22 @@ final class NavbarTest extends Unit
|
|||
},
|
||||
]);
|
||||
|
||||
$excludedModules = [
|
||||
'EmailText',
|
||||
'TeamMemberships',
|
||||
'TeamSets',
|
||||
'TeamSetModule'
|
||||
];
|
||||
|
||||
$moduleRegistry = new ModuleRegistryHandler(
|
||||
$projectDir,
|
||||
$legacyDir,
|
||||
$legacySessionName,
|
||||
$defaultSessionName,
|
||||
$legacyScope,
|
||||
$excludedModules
|
||||
);
|
||||
|
||||
$this->navbarHandler = new NavbarHandler($projectDir,
|
||||
$legacyDir,
|
||||
$legacySessionName,
|
||||
|
@ -176,7 +193,8 @@ final class NavbarTest extends Unit
|
|||
$legacyScope,
|
||||
$menuItemMap,
|
||||
$moduleNameMapper,
|
||||
$routeConverter
|
||||
$routeConverter,
|
||||
$moduleRegistry
|
||||
);
|
||||
$this->navbar = $this->navbarHandler->getNavbar();
|
||||
}
|
||||
|
@ -252,9 +270,9 @@ final class NavbarTest extends Unit
|
|||
'labelKey' => 'LBL_TABGROUP_SALES',
|
||||
// Ordered array
|
||||
'modules' => [
|
||||
'home',
|
||||
'accounts',
|
||||
'contacts',
|
||||
'home',
|
||||
'leads',
|
||||
]
|
||||
],
|
||||
|
@ -263,9 +281,9 @@ final class NavbarTest extends Unit
|
|||
'labelKey' => 'LBL_TABGROUP_MARKETING',
|
||||
// Ordered array
|
||||
'modules' => [
|
||||
'home',
|
||||
'accounts',
|
||||
'contacts',
|
||||
'home',
|
||||
'leads',
|
||||
]
|
||||
],
|
||||
|
@ -274,9 +292,9 @@ final class NavbarTest extends Unit
|
|||
'labelKey' => 'LBL_TABGROUP_SUPPORT',
|
||||
// Ordered array
|
||||
'modules' => [
|
||||
'home',
|
||||
'accounts',
|
||||
'contacts',
|
||||
'home'
|
||||
]
|
||||
],
|
||||
3 => [
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue