mirror of https://github.com/abpframework/abp.git
committed by
GitHub
26 changed files with 286 additions and 405 deletions
@ -1 +0,0 @@ |
|||
export * from './layout.actions'; |
|||
@ -1,11 +0,0 @@ |
|||
import { Layout } from '../models/layout'; |
|||
|
|||
export class AddNavigationElement { |
|||
static readonly type = '[Layout] Add Navigation Element'; |
|||
constructor(public payload: Layout.NavigationElement | Layout.NavigationElement[]) {} |
|||
} |
|||
|
|||
export class RemoveNavigationElementByName { |
|||
static readonly type = '[Layout] Remove Navigation ElementByName'; |
|||
constructor(public name: string) {} |
|||
} |
|||
@ -0,0 +1,67 @@ |
|||
import { ApplicationConfiguration, AuthService, ConfigState } from '@abp/ng.core'; |
|||
import { Component, OnInit } from '@angular/core'; |
|||
import { Router } from '@angular/router'; |
|||
import { Select } from '@ngxs/store'; |
|||
import { Observable } from 'rxjs'; |
|||
|
|||
@Component({ |
|||
selector: 'abp-current-user', |
|||
// tslint:disable-next-line: component-max-inline-declarations
|
|||
template: ` |
|||
<ng-template #loginBtn> |
|||
<a role="button" class="nav-link" routerLink="/account/login">{{ |
|||
'AbpAccount::Login' | abpLocalization |
|||
}}</a> |
|||
</ng-template> |
|||
<div |
|||
*ngIf="(currentUser$ | async)?.isAuthenticated; else loginBtn" |
|||
ngbDropdown |
|||
class="dropdown" |
|||
#currentUserDropdown="ngbDropdown" |
|||
display="static" |
|||
> |
|||
<a |
|||
ngbDropdownToggle |
|||
class="nav-link" |
|||
href="javascript:void(0)" |
|||
role="button" |
|||
id="dropdownMenuLink" |
|||
data-toggle="dropdown" |
|||
aria-haspopup="true" |
|||
aria-expanded="false" |
|||
> |
|||
{{ (currentUser$ | async)?.userName }} |
|||
</a> |
|||
<div |
|||
class="dropdown-menu dropdown-menu-right border-0 shadow-sm" |
|||
aria-labelledby="dropdownMenuLink" |
|||
[class.d-block]="smallScreen && currentUserDropdown.isOpen()" |
|||
> |
|||
<a class="dropdown-item" routerLink="/account/manage-profile" |
|||
><i class="fa fa-cog mr-1"></i>{{ 'AbpAccount::ManageYourProfile' | abpLocalization }}</a |
|||
> |
|||
<a class="dropdown-item" href="javascript:void(0)" (click)="logout()" |
|||
><i class="fa fa-power-off mr-1"></i>{{ 'AbpUi::Logout' | abpLocalization }}</a |
|||
> |
|||
</div> |
|||
</div> |
|||
`,
|
|||
}) |
|||
export class CurrentUserComponent implements OnInit { |
|||
@Select(ConfigState.getOne('currentUser')) |
|||
currentUser$: Observable<ApplicationConfiguration.CurrentUser>; |
|||
|
|||
get smallScreen(): boolean { |
|||
return window.innerWidth < 992; |
|||
} |
|||
|
|||
constructor(private authService: AuthService, private router: Router) {} |
|||
|
|||
ngOnInit() {} |
|||
|
|||
logout() { |
|||
this.authService.logout().subscribe(() => { |
|||
this.router.navigate(['/'], { state: { redirectUrl: this.router.url } }); |
|||
}); |
|||
} |
|||
} |
|||
@ -0,0 +1,88 @@ |
|||
import { Component, OnInit, Input } from '@angular/core'; |
|||
import { Store, Select } from '@ngxs/store'; |
|||
import { SetLanguage, ConfigState, ApplicationConfiguration, SessionState } from '@abp/ng.core'; |
|||
import { Observable } from 'rxjs'; |
|||
import { map } from 'rxjs/operators'; |
|||
import snq from 'snq'; |
|||
|
|||
@Component({ |
|||
selector: 'abp-languages', |
|||
// tslint:disable-next-line: component-max-inline-declarations
|
|||
template: ` |
|||
<div |
|||
*ngIf="(dropdownLanguages$ | async)?.length > 0" |
|||
class="dropdown" |
|||
ngbDropdown |
|||
#languageDropdown="ngbDropdown" |
|||
display="static" |
|||
> |
|||
<a |
|||
ngbDropdownToggle |
|||
class="nav-link" |
|||
href="javascript:void(0)" |
|||
role="button" |
|||
id="dropdownMenuLink" |
|||
data-toggle="dropdown" |
|||
aria-haspopup="true" |
|||
aria-expanded="false" |
|||
> |
|||
{{ defaultLanguage$ | async }} |
|||
</a> |
|||
<div |
|||
class="dropdown-menu dropdown-menu-right border-0 shadow-sm" |
|||
aria-labelledby="dropdownMenuLink" |
|||
[class.d-block]="smallScreen && languageDropdown.isOpen()" |
|||
> |
|||
<a |
|||
*ngFor="let lang of dropdownLanguages$ | async" |
|||
href="javascript:void(0)" |
|||
class="dropdown-item" |
|||
(click)="onChangeLang(lang.cultureName)" |
|||
>{{ lang?.displayName }}</a |
|||
> |
|||
</div> |
|||
</div> |
|||
`,
|
|||
}) |
|||
export class LanguagesComponent implements OnInit { |
|||
get smallScreen(): boolean { |
|||
return window.innerWidth < 992; |
|||
} |
|||
|
|||
@Select(ConfigState.getDeep('localization.languages')) |
|||
languages$: Observable<ApplicationConfiguration.Language[]>; |
|||
|
|||
get defaultLanguage$(): Observable<string> { |
|||
return this.languages$.pipe( |
|||
map( |
|||
languages => |
|||
snq( |
|||
() => languages.find(lang => lang.cultureName === this.selectedLangCulture).displayName, |
|||
), |
|||
'', |
|||
), |
|||
); |
|||
} |
|||
|
|||
get dropdownLanguages$(): Observable<ApplicationConfiguration.Language[]> { |
|||
return this.languages$.pipe( |
|||
map( |
|||
languages => |
|||
snq(() => languages.filter(lang => lang.cultureName !== this.selectedLangCulture)), |
|||
[], |
|||
), |
|||
); |
|||
} |
|||
|
|||
get selectedLangCulture(): string { |
|||
return this.store.selectSnapshot(SessionState.getLanguage); |
|||
} |
|||
|
|||
constructor(private store: Store) {} |
|||
|
|||
ngOnInit() {} |
|||
|
|||
onChangeLang(cultureName: string) { |
|||
this.store.dispatch(new SetLanguage(cultureName)); |
|||
} |
|||
} |
|||
@ -1,81 +1,16 @@ |
|||
<ul class="navbar-nav"> |
|||
<ng-container |
|||
*ngFor="let element of rightPartElements; trackBy: trackByFn" |
|||
[ngTemplateOutlet]="element" |
|||
[ngTemplateOutletContext]="{ smallScreen: smallScreen }" |
|||
></ng-container> |
|||
</ul> |
|||
|
|||
<ng-template #language let-smallScreen="smallScreen"> |
|||
<li *ngIf="(dropdownLanguages$ | async)?.length > 0" class="nav-item"> |
|||
<div class="dropdown" ngbDropdown #languageDropdown="ngbDropdown" display="static"> |
|||
<a |
|||
ngbDropdownToggle |
|||
class="nav-link" |
|||
href="javascript:void(0)" |
|||
role="button" |
|||
id="dropdownMenuLink" |
|||
data-toggle="dropdown" |
|||
aria-haspopup="true" |
|||
aria-expanded="false" |
|||
> |
|||
{{ defaultLanguage$ | async }} |
|||
</a> |
|||
<div |
|||
class="dropdown-menu dropdown-menu-right border-0 shadow-sm" |
|||
aria-labelledby="dropdownMenuLink" |
|||
[class.d-block]="smallScreen && languageDropdown.isOpen()" |
|||
> |
|||
<a |
|||
*ngFor="let lang of dropdownLanguages$ | async" |
|||
href="javascript:void(0)" |
|||
class="dropdown-item" |
|||
(click)="onChangeLang(lang.cultureName)" |
|||
>{{ lang?.displayName }}</a |
|||
> |
|||
</div> |
|||
</div> |
|||
</li> |
|||
</ng-template> |
|||
<li |
|||
class="nav-item d-flex align-items-center" |
|||
*ngFor="let item of navItems.items$ | async; trackBy: trackByFn" |
|||
[abpPermission]="item.requiredPolicy" |
|||
> |
|||
<ng-container |
|||
*ngIf="item.component; else htmlTemplate" |
|||
[ngComponentOutlet]="item.component" |
|||
></ng-container> |
|||
|
|||
<ng-template #currentUser let-smallScreen="smallScreen"> |
|||
<li class="nav-item"> |
|||
<ng-template #loginBtn> |
|||
<a role="button" class="nav-link" routerLink="/account/login">{{ |
|||
'AbpAccount::Login' | abpLocalization |
|||
}}</a> |
|||
<ng-template #htmlTemplate> |
|||
<div [innerHTML]="item.html" (click)="item.action ? item.action() : null"></div> |
|||
</ng-template> |
|||
<div |
|||
*ngIf="(currentUser$ | async)?.isAuthenticated; else loginBtn" |
|||
ngbDropdown |
|||
class="dropdown" |
|||
#currentUserDropdown="ngbDropdown" |
|||
display="static" |
|||
> |
|||
<a |
|||
ngbDropdownToggle |
|||
class="nav-link" |
|||
href="javascript:void(0)" |
|||
role="button" |
|||
id="dropdownMenuLink" |
|||
data-toggle="dropdown" |
|||
aria-haspopup="true" |
|||
aria-expanded="false" |
|||
> |
|||
{{ (currentUser$ | async)?.userName }} |
|||
</a> |
|||
<div |
|||
class="dropdown-menu dropdown-menu-right border-0 shadow-sm" |
|||
aria-labelledby="dropdownMenuLink" |
|||
[class.d-block]="smallScreen && currentUserDropdown.isOpen()" |
|||
> |
|||
<a class="dropdown-item" routerLink="/account/manage-profile" |
|||
><i class="fa fa-cog mr-1"></i>{{ 'AbpAccount::ManageYourProfile' | abpLocalization }}</a |
|||
> |
|||
<a class="dropdown-item" href="javascript:void(0)" (click)="logout()" |
|||
><i class="fa fa-power-off mr-1"></i>{{ 'AbpUi::Logout' | abpLocalization }}</a |
|||
> |
|||
</div> |
|||
</div> |
|||
</li> |
|||
</ng-template> |
|||
</ul> |
|||
|
|||
@ -1,125 +1,12 @@ |
|||
import { |
|||
ABP, |
|||
ApplicationConfiguration, |
|||
AuthService, |
|||
ConfigState, |
|||
SessionState, |
|||
SetLanguage, |
|||
takeUntilDestroy, |
|||
} from '@abp/ng.core'; |
|||
import { |
|||
AfterViewInit, |
|||
Component, |
|||
Input, |
|||
OnDestroy, |
|||
TemplateRef, |
|||
TrackByFunction, |
|||
ViewChild, |
|||
} from '@angular/core'; |
|||
import { Navigate, RouterState } from '@ngxs/router-plugin'; |
|||
import { Select, Store } from '@ngxs/store'; |
|||
import compare from 'just-compare'; |
|||
import { Observable } from 'rxjs'; |
|||
import { filter, map } from 'rxjs/operators'; |
|||
import snq from 'snq'; |
|||
import { AddNavigationElement } from '../../actions/layout.actions'; |
|||
import { eNavigationElementNames } from '../../enums/navigation-element-names'; |
|||
import { Layout } from '../../models/layout'; |
|||
import { LayoutState } from '../../states/layout.state'; |
|||
import { NavItem, NavItemsService } from '@abp/ng.theme.shared'; |
|||
import { Component, Input, TrackByFunction } from '@angular/core'; |
|||
|
|||
@Component({ |
|||
selector: 'abp-nav-items', |
|||
templateUrl: 'nav-items.component.html', |
|||
}) |
|||
export class NavItemsComponent implements AfterViewInit, OnDestroy { |
|||
@Select(LayoutState.getNavigationElements) |
|||
navElements$: Observable<Layout.NavigationElement[]>; |
|||
export class NavItemsComponent { |
|||
trackByFn: TrackByFunction<NavItem> = (_, element) => element.id; |
|||
|
|||
@Select(ConfigState.getOne('currentUser')) |
|||
currentUser$: Observable<ApplicationConfiguration.CurrentUser>; |
|||
|
|||
@Select(ConfigState.getDeep('localization.languages')) |
|||
languages$: Observable<ApplicationConfiguration.Language[]>; |
|||
|
|||
@ViewChild('currentUser', { static: false, read: TemplateRef }) |
|||
currentUserRef: TemplateRef<any>; |
|||
|
|||
@ViewChild('language', { static: false, read: TemplateRef }) |
|||
languageRef: TemplateRef<any>; |
|||
|
|||
@Input() |
|||
smallScreen: boolean; |
|||
|
|||
rightPartElements: TemplateRef<any>[] = []; |
|||
|
|||
trackByFn: TrackByFunction<ABP.Route> = (_, element) => element.name; |
|||
|
|||
get defaultLanguage$(): Observable<string> { |
|||
return this.languages$.pipe( |
|||
map( |
|||
languages => |
|||
snq( |
|||
() => languages.find(lang => lang.cultureName === this.selectedLangCulture).displayName, |
|||
), |
|||
'', |
|||
), |
|||
); |
|||
} |
|||
|
|||
get dropdownLanguages$(): Observable<ApplicationConfiguration.Language[]> { |
|||
return this.languages$.pipe( |
|||
map( |
|||
languages => |
|||
snq(() => languages.filter(lang => lang.cultureName !== this.selectedLangCulture)), |
|||
[], |
|||
), |
|||
); |
|||
} |
|||
|
|||
get selectedLangCulture(): string { |
|||
return this.store.selectSnapshot(SessionState.getLanguage); |
|||
} |
|||
|
|||
constructor(private store: Store, private authService: AuthService) {} |
|||
|
|||
ngAfterViewInit() { |
|||
const navigations = this.store |
|||
.selectSnapshot(LayoutState.getNavigationElements) |
|||
.map(({ name }) => name); |
|||
|
|||
if (navigations.indexOf(eNavigationElementNames.Language) < 0) { |
|||
this.store.dispatch( |
|||
new AddNavigationElement([ |
|||
{ element: this.languageRef, order: 4, name: eNavigationElementNames.Language }, |
|||
{ element: this.currentUserRef, order: 5, name: eNavigationElementNames.User }, |
|||
]), |
|||
); |
|||
} |
|||
|
|||
this.navElements$ |
|||
.pipe( |
|||
map(elements => elements.map(({ element }) => element)), |
|||
filter(elements => !compare(elements, this.rightPartElements)), |
|||
takeUntilDestroy(this), |
|||
) |
|||
.subscribe(elements => { |
|||
setTimeout(() => (this.rightPartElements = elements), 0); |
|||
}); |
|||
} |
|||
|
|||
ngOnDestroy() {} |
|||
|
|||
onChangeLang(cultureName: string) { |
|||
this.store.dispatch(new SetLanguage(cultureName)); |
|||
} |
|||
|
|||
logout() { |
|||
this.authService.logout().subscribe(() => { |
|||
this.store.dispatch( |
|||
new Navigate(['/'], null, { |
|||
state: { redirectUrl: this.store.selectSnapshot(RouterState).state.url }, |
|||
}), |
|||
); |
|||
}); |
|||
} |
|||
constructor(public readonly navItems: NavItemsService) {} |
|||
} |
|||
|
|||
@ -1,4 +0,0 @@ |
|||
export const enum eNavigationElementNames { |
|||
Language = 'LanguageRef', |
|||
User = 'CurrentUserRef', |
|||
} |
|||
@ -1 +1,2 @@ |
|||
export * from './nav-item.provider'; |
|||
export * from './styles.provider'; |
|||
|
|||
@ -0,0 +1,31 @@ |
|||
import { NavItemsService } from '@abp/ng.theme.shared'; |
|||
import { APP_INITIALIZER } from '@angular/core'; |
|||
import { CurrentUserComponent } from '../components/nav-items/current-user.component'; |
|||
import { LanguagesComponent } from '../components/nav-items/languages.component'; |
|||
import { eThemeBasicComponents } from '../enums/components'; |
|||
|
|||
export const BASIC_THEME_NAV_ITEM_PROVIDERS = [ |
|||
{ |
|||
provide: APP_INITIALIZER, |
|||
useFactory: configureNavItems, |
|||
deps: [NavItemsService], |
|||
multi: true, |
|||
}, |
|||
]; |
|||
|
|||
export function configureNavItems(navItems: NavItemsService) { |
|||
return () => { |
|||
navItems.addItems([ |
|||
{ |
|||
id: eThemeBasicComponents.Languages, |
|||
order: 100, |
|||
component: LanguagesComponent, |
|||
}, |
|||
{ |
|||
id: eThemeBasicComponents.CurrentUser, |
|||
order: 101, |
|||
component: CurrentUserComponent, |
|||
}, |
|||
]); |
|||
}; |
|||
} |
|||
@ -1 +0,0 @@ |
|||
export * from './layout-state.service'; |
|||
@ -1,24 +0,0 @@ |
|||
import { Injectable } from '@angular/core'; |
|||
import { Store } from '@ngxs/store'; |
|||
import { LayoutState } from '../states/layout.state'; |
|||
import { AddNavigationElement, RemoveNavigationElementByName } from '../actions'; |
|||
import { Layout } from '../models/layout'; |
|||
|
|||
@Injectable({ providedIn: 'root' }) |
|||
export class LayoutStateService { |
|||
constructor(private store: Store) {} |
|||
|
|||
getNavigationElements() { |
|||
return this.store.selectSnapshot(LayoutState.getNavigationElements); |
|||
} |
|||
|
|||
dispatchAddNavigationElement(...args: ConstructorParameters<typeof AddNavigationElement>) { |
|||
return this.store.dispatch(new AddNavigationElement(...args)); |
|||
} |
|||
|
|||
dispatchRemoveNavigationElementByName( |
|||
...args: ConstructorParameters<typeof RemoveNavigationElementByName> |
|||
) { |
|||
return this.store.dispatch(new RemoveNavigationElementByName(...args)); |
|||
} |
|||
} |
|||
@ -1 +0,0 @@ |
|||
export * from './layout.state'; |
|||
@ -1,67 +0,0 @@ |
|||
import { Injectable } from '@angular/core'; |
|||
import { Action, Selector, State, StateContext } from '@ngxs/store'; |
|||
import snq from 'snq'; |
|||
import { AddNavigationElement, RemoveNavigationElementByName } from '../actions/layout.actions'; |
|||
import { Layout } from '../models/layout'; |
|||
|
|||
@State<Layout.State>({ |
|||
name: 'LayoutState', |
|||
defaults: { navigationElements: [] } as Layout.State, |
|||
}) |
|||
@Injectable() |
|||
export class LayoutState { |
|||
@Selector() |
|||
static getNavigationElements({ navigationElements }: Layout.State): Layout.NavigationElement[] { |
|||
return navigationElements; |
|||
} |
|||
|
|||
@Action(AddNavigationElement) |
|||
layoutAddAction( |
|||
{ getState, patchState }: StateContext<Layout.State>, |
|||
{ payload = [] }: AddNavigationElement, |
|||
) { |
|||
let { navigationElements } = getState(); |
|||
|
|||
if (!Array.isArray(payload)) { |
|||
payload = [payload]; |
|||
} |
|||
|
|||
if (navigationElements.length) { |
|||
payload = snq( |
|||
() => |
|||
(payload as Layout.NavigationElement[]).filter( |
|||
({ name }) => navigationElements.findIndex(nav => nav.name === name) < 0, |
|||
), |
|||
[], |
|||
); |
|||
} |
|||
|
|||
if (!payload.length) return; |
|||
|
|||
navigationElements = [...navigationElements, ...payload] |
|||
.map(element => ({ ...element, order: element.order || 99 })) |
|||
.sort((a, b) => a.order - b.order); |
|||
|
|||
return patchState({ |
|||
navigationElements, |
|||
}); |
|||
} |
|||
|
|||
@Action(RemoveNavigationElementByName) |
|||
layoutRemoveAction( |
|||
{ getState, patchState }: StateContext<Layout.State>, |
|||
{ name }: RemoveNavigationElementByName, |
|||
) { |
|||
let { navigationElements } = getState(); |
|||
|
|||
const index = navigationElements.findIndex(element => element.name === name); |
|||
|
|||
if (index > -1) { |
|||
navigationElements = navigationElements.splice(index, 1); |
|||
} |
|||
|
|||
return patchState({ |
|||
navigationElements, |
|||
}); |
|||
} |
|||
} |
|||
@ -1,57 +0,0 @@ |
|||
import { createServiceFactory, SpectatorService, SpyObject } from '@ngneat/spectator/jest'; |
|||
import { Store } from '@ngxs/store'; |
|||
import * as LayoutActions from '../actions'; |
|||
import { LayoutStateService } from '../services/layout-state.service'; |
|||
import { LayoutState } from '../states/layout.state'; |
|||
describe('LayoutStateService', () => { |
|||
let service: LayoutStateService; |
|||
let spectator: SpectatorService<LayoutStateService>; |
|||
let store: SpyObject<Store>; |
|||
|
|||
const createService = createServiceFactory({ service: LayoutStateService, mocks: [Store] }); |
|||
beforeEach(() => { |
|||
spectator = createService(); |
|||
service = spectator.service; |
|||
store = spectator.get(Store); |
|||
}); |
|||
|
|||
test('should have the all LayoutState static methods', () => { |
|||
const reg = /(?<=static )(.*)(?=\()/gm; |
|||
LayoutState.toString() |
|||
.match(reg) |
|||
.forEach(fnName => { |
|||
expect(service[fnName]).toBeTruthy(); |
|||
|
|||
const spy = jest.spyOn(store, 'selectSnapshot'); |
|||
spy.mockClear(); |
|||
|
|||
const isDynamicSelector = LayoutState[fnName].name !== 'memoized'; |
|||
|
|||
if (isDynamicSelector) { |
|||
LayoutState[fnName] = jest.fn((...args) => args); |
|||
service[fnName]('test', 0, {}); |
|||
expect(LayoutState[fnName]).toHaveBeenCalledWith('test', 0, {}); |
|||
} else { |
|||
service[fnName](); |
|||
expect(spy).toHaveBeenCalledWith(LayoutState[fnName]); |
|||
} |
|||
}); |
|||
}); |
|||
|
|||
test('should have a dispatch method for every LayoutState action', () => { |
|||
const reg = /(?<=dispatch)(\w+)(?=\()/gm; |
|||
LayoutStateService.toString() |
|||
.match(reg) |
|||
.forEach(fnName => { |
|||
expect(LayoutActions[fnName]).toBeTruthy(); |
|||
|
|||
const spy = jest.spyOn(store, 'dispatch'); |
|||
spy.mockClear(); |
|||
|
|||
const params = Array.from(new Array(LayoutActions[fnName].length)); |
|||
|
|||
service[`dispatch${fnName}`](...params); |
|||
expect(spy).toHaveBeenCalledWith(new LayoutActions[fnName](...params)); |
|||
}); |
|||
}); |
|||
}); |
|||
@ -0,0 +1,10 @@ |
|||
import { Type } from '@angular/core'; |
|||
|
|||
export interface NavItem { |
|||
id: string | number; |
|||
component?: Type<any>; |
|||
html?: string; |
|||
action?: () => void; |
|||
order?: number; |
|||
requiredPolicy?: string; |
|||
} |
|||
@ -1,3 +1,4 @@ |
|||
export * from './confirmation.service'; |
|||
export * from './modal.service'; |
|||
export * from './toaster.service'; |
|||
export * from './nav-items.service'; |
|||
|
|||
@ -0,0 +1,49 @@ |
|||
import { Injectable } from '@angular/core'; |
|||
import { BehaviorSubject, Observable } from 'rxjs'; |
|||
import { NavItem } from '../models/nav-item'; |
|||
|
|||
@Injectable({ providedIn: 'root' }) |
|||
export class NavItemsService { |
|||
private _items$ = new BehaviorSubject<NavItem[]>([]); |
|||
|
|||
get items(): NavItem[] { |
|||
return this._items$.value; |
|||
} |
|||
|
|||
get items$(): Observable<NavItem[]> { |
|||
return this._items$.asObservable(); |
|||
} |
|||
|
|||
addItems(items: NavItem[]) { |
|||
this._items$.next([...this.items, ...items].sort(sortItems)); |
|||
} |
|||
|
|||
removeItem(id: string | number) { |
|||
const index = this.items.findIndex(item => item.id === id); |
|||
|
|||
if (index > -1) { |
|||
this._items$.next([...this.items.slice(0, index), ...this.items.slice(index + 1)]); |
|||
} |
|||
} |
|||
|
|||
patchItem(id: string | number, item: Partial<Omit<NavItem, 'id'>>) { |
|||
const index = this.items.findIndex(i => i.id === id); |
|||
|
|||
if (index > -1) { |
|||
this._items$.next( |
|||
[ |
|||
...this.items.slice(0, index), |
|||
{ ...this.items[index], ...item }, |
|||
...this.items.slice(index + 1), |
|||
].sort(sortItems), |
|||
); |
|||
} |
|||
} |
|||
} |
|||
|
|||
function sortItems(a: NavItem, b: NavItem) { |
|||
if (!a.order) return 1; |
|||
if (!b.order) return -1; |
|||
|
|||
return a.order < b.order ? -1 : 1; |
|||
} |
|||
@ -1,4 +1,3 @@ |
|||
export * from './date-parser-formatter'; |
|||
export * from './nav-items'; |
|||
export * from './validation-utils'; |
|||
export * from './widget-utils'; |
|||
|
|||
@ -1,22 +0,0 @@ |
|||
import { Type } from '@angular/core'; |
|||
import { ReplaySubject } from 'rxjs'; |
|||
|
|||
export interface NavItem { |
|||
component?: Type<any>; |
|||
html?: string; |
|||
action?: () => void; |
|||
order?: number; |
|||
permission?: string; |
|||
} |
|||
|
|||
const navItems: NavItem[] = []; |
|||
const navItems$ = new ReplaySubject<NavItem[]>(1); |
|||
|
|||
export function addNavItem(item: NavItem) { |
|||
navItems.push(item); |
|||
navItems$.next(navItems.sort((a, b) => (a.order ? a.order - b.order : 1))); |
|||
} |
|||
|
|||
export function getNavItems() { |
|||
return navItems$.asObservable(); |
|||
} |
|||
Loading…
Reference in new issue