mirror of https://github.com/abpframework/abp
Merge pull request #3851 from abpframework/feat/layout-components
Split ApplicaitonLayoutComponent into smaller componentspull/3853/head
commit
d176d8c1f8
@ -1,4 +1,7 @@
|
||||
export * from './account-layout/account-layout.component';
|
||||
export * from './application-layout/application-layout.component';
|
||||
export * from './empty-layout/empty-layout.component';
|
||||
export * from './logo/logo.component';
|
||||
export * from './nav-items/nav-items.component';
|
||||
export * from './routes/routes.component';
|
||||
export * from './validation-error/validation-error.component';
|
||||
|
@ -0,0 +1,29 @@
|
||||
import { Config, ConfigState } from '@abp/ng.core';
|
||||
import { Component } from '@angular/core';
|
||||
import { Store } from '@ngxs/store';
|
||||
|
||||
@Component({
|
||||
selector: 'abp-logo',
|
||||
template: `
|
||||
<a class="navbar-brand" routerLink="/">
|
||||
<img
|
||||
*ngIf="appInfo.logoUrl; else appName"
|
||||
[src]="appInfo.logoUrl"
|
||||
[alt]="appInfo.name"
|
||||
width="100%"
|
||||
height="auto"
|
||||
/>
|
||||
</a>
|
||||
|
||||
<ng-template #appName>
|
||||
{{ appInfo.name }}
|
||||
</ng-template>
|
||||
`,
|
||||
})
|
||||
export class LogoComponent {
|
||||
get appInfo(): Config.Application {
|
||||
return this.store.selectSnapshot(ConfigState.getApplicationInfo);
|
||||
}
|
||||
|
||||
constructor(private store: Store) {}
|
||||
}
|
@ -0,0 +1,81 @@
|
||||
<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>
|
||||
|
||||
<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>
|
||||
<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>
|
@ -0,0 +1,125 @@
|
||||
import {
|
||||
Component,
|
||||
AfterViewInit,
|
||||
TrackByFunction,
|
||||
TemplateRef,
|
||||
ViewChild,
|
||||
OnDestroy,
|
||||
Input,
|
||||
} from '@angular/core';
|
||||
import {
|
||||
ABP,
|
||||
takeUntilDestroy,
|
||||
SetLanguage,
|
||||
AuthService,
|
||||
ConfigState,
|
||||
ApplicationConfiguration,
|
||||
SessionState,
|
||||
} from '@abp/ng.core';
|
||||
import { LayoutState } from '../../states/layout.state';
|
||||
import { Store, Select } from '@ngxs/store';
|
||||
import { eNavigationElementNames } from '../../enums/navigation-element-names';
|
||||
import { AddNavigationElement } from '../../actions/layout.actions';
|
||||
import { map, filter } from 'rxjs/operators';
|
||||
import { Observable } from 'rxjs';
|
||||
import { Layout } from '../../models/layout';
|
||||
import { Navigate, RouterState } from '@ngxs/router-plugin';
|
||||
import snq from 'snq';
|
||||
import compare from 'just-compare';
|
||||
|
||||
@Component({
|
||||
selector: 'abp-nav-items',
|
||||
templateUrl: 'nav-items.component.html',
|
||||
})
|
||||
export class NavItemsComponent implements AfterViewInit, OnDestroy {
|
||||
@Select(LayoutState.getNavigationElements)
|
||||
navElements$: Observable<Layout.NavigationElement[]>;
|
||||
|
||||
@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.FullRoute> = (_, element) => element;
|
||||
|
||||
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 },
|
||||
}),
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
@ -0,0 +1,111 @@
|
||||
<ul class="navbar-nav">
|
||||
<ng-container
|
||||
*ngFor="let route of visibleRoutes$ | async; trackBy: trackByFn"
|
||||
[ngTemplateOutlet]="route?.children?.length ? dropdownLink : defaultLink"
|
||||
[ngTemplateOutletContext]="{ $implicit: route }"
|
||||
>
|
||||
</ng-container>
|
||||
|
||||
<ng-template #defaultLink let-route>
|
||||
<li class="nav-item" *abpPermission="route.requiredPolicy">
|
||||
<a class="nav-link" [routerLink]="[route.url]"
|
||||
><i *ngIf="route.iconClass" [ngClass]="route.iconClass"></i>
|
||||
{{ route.name | abpLocalization }}</a
|
||||
>
|
||||
</li>
|
||||
</ng-template>
|
||||
|
||||
<ng-template #dropdownLink let-route>
|
||||
<li
|
||||
#navbarRootDropdown
|
||||
*abpPermission="route.requiredPolicy"
|
||||
[abpVisibility]="routeContainer"
|
||||
class="nav-item dropdown"
|
||||
display="static"
|
||||
(click)="
|
||||
navbarRootDropdown.expand
|
||||
? (navbarRootDropdown.expand = false)
|
||||
: (navbarRootDropdown.expand = true)
|
||||
"
|
||||
>
|
||||
<a
|
||||
class="nav-link dropdown-toggle"
|
||||
data-toggle="dropdown"
|
||||
aria-haspopup="true"
|
||||
aria-expanded="false"
|
||||
href="javascript:void(0)"
|
||||
>
|
||||
<i *ngIf="route.iconClass" [ngClass]="route.iconClass"></i>
|
||||
{{ route.name | abpLocalization }}
|
||||
</a>
|
||||
<div
|
||||
#routeContainer
|
||||
class="dropdown-menu border-0 shadow-sm"
|
||||
(click)="$event.preventDefault(); $event.stopPropagation()"
|
||||
[class.d-block]="smallScreen && navbarRootDropdown.expand"
|
||||
>
|
||||
<ng-template
|
||||
#forTemplate
|
||||
ngFor
|
||||
[ngForOf]="route.children"
|
||||
[ngForTrackBy]="trackByFn"
|
||||
[ngForTemplate]="childWrapper"
|
||||
></ng-template>
|
||||
</div>
|
||||
</li>
|
||||
</ng-template>
|
||||
|
||||
<ng-template #childWrapper let-child>
|
||||
<ng-template
|
||||
[ngTemplateOutlet]="child?.children?.length ? dropdownChild : defaultChild"
|
||||
[ngTemplateOutletContext]="{ $implicit: child }"
|
||||
></ng-template>
|
||||
</ng-template>
|
||||
|
||||
<ng-template #defaultChild let-child>
|
||||
<div class="dropdown-submenu" *abpPermission="child.requiredPolicy">
|
||||
<a class="dropdown-item" [routerLink]="[child.url]">
|
||||
<i *ngIf="child.iconClass" [ngClass]="child.iconClass"></i>
|
||||
{{ child.name | abpLocalization }}</a
|
||||
>
|
||||
</div>
|
||||
</ng-template>
|
||||
|
||||
<ng-template #dropdownChild let-child>
|
||||
<div
|
||||
[abpVisibility]="childrenContainer"
|
||||
class="dropdown-submenu"
|
||||
ngbDropdown
|
||||
#dropdownSubmenu="ngbDropdown"
|
||||
[display]="isDropdownChildDynamic ? 'dynamic' : 'static'"
|
||||
placement="right-top"
|
||||
[autoClose]="true"
|
||||
*abpPermission="child.requiredPolicy"
|
||||
(openChange)="openChange($event, childrenContainer)"
|
||||
>
|
||||
<div ngbDropdownToggle [class.dropdown-toggle]="false">
|
||||
<a
|
||||
abpEllipsis="210px"
|
||||
[abpEllipsisEnabled]="isDropdownChildDynamic"
|
||||
role="button"
|
||||
class="btn d-block text-left dropdown-toggle"
|
||||
>
|
||||
<i *ngIf="child.iconClass" [ngClass]="child.iconClass"></i>
|
||||
{{ child.name | abpLocalization }}
|
||||
</a>
|
||||
</div>
|
||||
<div
|
||||
#childrenContainer
|
||||
class="dropdown-menu border-0 shadow-sm"
|
||||
[class.d-block]="smallScreen && dropdownSubmenu.isOpen()"
|
||||
>
|
||||
<ng-template
|
||||
ngFor
|
||||
[ngForOf]="child.children"
|
||||
[ngForTrackBy]="trackByFn"
|
||||
[ngForTemplate]="childWrapper"
|
||||
></ng-template>
|
||||
</div>
|
||||
</div>
|
||||
</ng-template>
|
||||
</ul>
|
@ -0,0 +1,51 @@
|
||||
import { Component, OnInit, TrackByFunction, Input, Renderer2 } from '@angular/core';
|
||||
import { Observable } from 'rxjs';
|
||||
import { ABP, ConfigState } from '@abp/ng.core';
|
||||
import { map } from 'rxjs/operators';
|
||||
import { Select } from '@ngxs/store';
|
||||
|
||||
@Component({
|
||||
selector: 'abp-routes',
|
||||
templateUrl: 'routes.component.html',
|
||||
})
|
||||
export class RoutesComponent {
|
||||
@Select(ConfigState.getOne('routes'))
|
||||
routes$: Observable<ABP.FullRoute[]>;
|
||||
|
||||
@Input()
|
||||
smallScreen: boolean;
|
||||
|
||||
@Input()
|
||||
isDropdownChildDynamic: boolean;
|
||||
|
||||
get visibleRoutes$(): Observable<ABP.FullRoute[]> {
|
||||
return this.routes$.pipe(map(routes => getVisibleRoutes(routes)));
|
||||
}
|
||||
|
||||
trackByFn: TrackByFunction<ABP.FullRoute> = (_, item) => item.name;
|
||||
|
||||
constructor(private renderer: Renderer2) {}
|
||||
|
||||
openChange(event: boolean, childrenContainer: HTMLDivElement) {
|
||||
if (!event) {
|
||||
Object.keys(childrenContainer.style)
|
||||
.filter(key => Number.isInteger(+key))
|
||||
.forEach(key => {
|
||||
this.renderer.removeStyle(childrenContainer, childrenContainer.style[key]);
|
||||
});
|
||||
this.renderer.removeStyle(childrenContainer, 'left');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function getVisibleRoutes(routes: ABP.FullRoute[]) {
|
||||
return routes.reduce((acc, val) => {
|
||||
if (val.invisible) return acc;
|
||||
|
||||
if (val.children && val.children.length) {
|
||||
val.children = getVisibleRoutes(val.children);
|
||||
}
|
||||
|
||||
return [...acc, val];
|
||||
}, []);
|
||||
}
|
Loading…
Reference in new issue