feat(theme-basic): create routes component

pull/3851/head
mehmet-erim 5 years ago
parent 99fb9b6e59
commit 5bb96ddc5b

@ -3,16 +3,8 @@
id="main-navbar"
style="min-height: 4rem;"
>
<div class="container ">
<a class="navbar-brand" routerLink="/">
<img
*ngIf="appInfo.logoUrl; else appName"
[src]="appInfo.logoUrl"
[alt]="appInfo.name"
width="100%"
height="auto"
/>
</a>
<div class="container">
<abp-logo *abpReplaceableTemplate="{ componentKey: logoComponentKey }"></abp-logo>
<button
class="navbar-toggler"
type="button"
@ -29,124 +21,17 @@
</div>
<ng-template #navigations>
<ul class="navbar-nav mx-auto">
<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>
<ul class="navbar-nav">
<ng-container
*ngFor="let element of rightPartElements; trackBy: trackElementByFn"
[ngTemplateOutlet]="element"
></ng-container>
</ul>
<abp-routes
*abpReplaceableTemplate="{ componentKey: routesComponentKey }"
class="mx-auto"
[smallScreen]="smallScreen"
[isDropdownChildDynamic]="isDropdownChildDynamic"
></abp-routes>
<abp-nav-items
*abpReplaceableTemplate="{ componentKey: navItemsComponentKey }"
[smallScreen]="smallScreen"
></abp-nav-items>
</ng-template>
</div>
</div>
@ -160,81 +45,3 @@
>
<router-outlet #outlet="outlet"></router-outlet>
</div>
<ng-template #appName>
{{ appInfo.name }}
</ng-template>
<ng-template #language>
<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>
<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>

@ -1,34 +1,10 @@
import {
ABP,
ApplicationConfiguration,
AuthService,
Config,
ConfigState,
eLayoutType,
SessionState,
SetLanguage,
takeUntilDestroy,
} from '@abp/ng.core';
import { eLayoutType, takeUntilDestroy } from '@abp/ng.core';
import { collapseWithMargin, slideFromBottom } from '@abp/ng.theme.shared';
import {
AfterViewInit,
Component,
OnDestroy,
Renderer2,
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 { fromEvent, Observable } from 'rxjs';
import { debounceTime, filter, map } from 'rxjs/operators';
import snq from 'snq';
import { AddNavigationElement } from '../../actions';
import { Layout } from '../../models/layout';
import { LayoutState } from '../../states';
import { eNavigationElementNames } from '../../enums/navigation-element-names';
import { AfterViewInit, Component, OnDestroy } from '@angular/core';
import { Store } from '@ngxs/store';
import { fromEvent } from 'rxjs';
import { debounceTime } from 'rxjs/operators';
import { eThemeBasicComponents } from '../../enums/components';
@Component({
selector: 'abp-layout-application',
@ -39,75 +15,19 @@ export class ApplicationLayoutComponent implements AfterViewInit, OnDestroy {
// required for dynamic component
static type = eLayoutType.application;
@Select(ConfigState.getOne('routes'))
routes$: Observable<ABP.FullRoute[]>;
@Select(ConfigState.getOne('currentUser'))
currentUser$: Observable<ApplicationConfiguration.CurrentUser>;
@Select(ConfigState.getDeep('localization.languages'))
languages$: Observable<ApplicationConfiguration.Language[]>;
@Select(LayoutState.getNavigationElements)
navElements$: Observable<Layout.NavigationElement[]>;
@ViewChild('currentUser', { static: false, read: TemplateRef })
currentUserRef: TemplateRef<any>;
@ViewChild('language', { static: false, read: TemplateRef })
languageRef: TemplateRef<any>;
isDropdownChildDynamic: boolean;
isCollapsed = true;
smallScreen: boolean; // do not set true or false
get appInfo(): Config.Application {
return this.store.selectSnapshot(ConfigState.getApplicationInfo);
}
get visibleRoutes$(): Observable<ABP.FullRoute[]> {
return this.routes$.pipe(map(routes => getVisibleRoutes(routes)));
}
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);
}
rightPartElements: TemplateRef<any>[] = [];
logoComponentKey = eThemeBasicComponents.Logo;
trackByFn: TrackByFunction<ABP.FullRoute> = (_, item) => item.name;
routesComponentKey = eThemeBasicComponents.Routes;
trackElementByFn: TrackByFunction<ABP.FullRoute> = (_, element) => element;
navItemsComponentKey = eThemeBasicComponents.NavItems;
constructor(
private store: Store,
private renderer: Renderer2,
private authService: AuthService,
) {}
constructor(private store: Store) {}
private checkWindowWidth() {
setTimeout(() => {
@ -128,29 +48,6 @@ export class ApplicationLayoutComponent implements AfterViewInit, OnDestroy {
}
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);
});
this.checkWindowWidth();
fromEvent(window, 'resize')
@ -161,41 +58,4 @@ export class ApplicationLayoutComponent implements AfterViewInit, OnDestroy {
}
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 },
}),
);
});
}
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];
}, []);
}

@ -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,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];
}, []);
}

@ -2,4 +2,7 @@ export const enum eThemeBasicComponents {
ApplicationLayout = 'Theme.ApplicationLayoutComponent',
AccountLayout = 'Theme.AccountLayoutComponent',
EmptyLayout = 'Theme.EmptyLayoutComponent',
Logo = 'Theme.LogoComponent',
Routes = 'Theme.RoutesComponent',
NavItems = 'Theme.NavItemsComponent',
}

@ -10,11 +10,20 @@ import { EmptyLayoutComponent } from './components/empty-layout/empty-layout.com
import { LayoutState } from './states/layout.state';
import { ValidationErrorComponent } from './components/validation-error/validation-error.component';
import { InitialService } from './services/initial.service';
import { LogoComponent } from './components/logo/logo.component';
import { RoutesComponent } from './components/routes/routes.component';
import { NavItemsComponent } from './components/nav-items/nav-items.component';
export const LAYOUTS = [ApplicationLayoutComponent, AccountLayoutComponent, EmptyLayoutComponent];
@NgModule({
declarations: [...LAYOUTS, ValidationErrorComponent],
declarations: [
...LAYOUTS,
ValidationErrorComponent,
LogoComponent,
NavItemsComponent,
RoutesComponent,
],
imports: [
CoreModule,
ThemeSharedModule,
@ -38,7 +47,7 @@ export const LAYOUTS = [ApplicationLayoutComponent, AccountLayoutComponent, Empt
errorTemplate: ValidationErrorComponent,
}),
],
exports: [...LAYOUTS],
exports: [...LAYOUTS, LogoComponent, ValidationErrorComponent],
entryComponents: [...LAYOUTS, ValidationErrorComponent],
})
export class ThemeBasicModule {

Loading…
Cancel
Save