mirror of https://github.com/abpframework/abp
Merge pull request #4404 from abpframework/refactor/nav-items
Created NavItemsServicepull/4493/head
commit
9824dfba27
@ -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