Merge pull request #1984 from abpframework/feat/ui-changes

feat: implement new html design
pull/2000/head
Mehmet Erim 6 years ago committed by GitHub
commit 21c39fe01b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -418,7 +418,7 @@
},
"root": "apps/dev-app",
"sourceRoot": "apps/dev-app/src",
"prefix": "app",
"prefix": "abp",
"architect": {
"build": {
"builder": "@angular-devkit/build-angular:browser",

@ -3,7 +3,7 @@ import { LAYOUTS } from '@abp/ng.theme.basic';
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { NgxsReduxDevtoolsPluginModule } from '@ngxs/devtools-plugin';
import { NgxsLoggerPluginModule } from '@ngxs/logger-plugin';
import { NgxsModule } from '@ngxs/store';
import { OAuthModule } from 'angular-oauth2-oidc';
import { environment } from '../environments/environment';
@ -16,6 +16,8 @@ import { IdentityConfigModule } from '@abp/ng.identity.config';
import { TenantManagementConfigModule } from '@abp/ng.tenant-management.config';
import { SettingManagementConfigModule } from '@abp/ng.setting-management.config';
const LOGGERS = [NgxsLoggerPluginModule.forRoot({ disabled: false })];
@NgModule({
declarations: [AppComponent],
imports: [
@ -26,6 +28,8 @@ import { SettingManagementConfigModule } from '@abp/ng.setting-management.config
},
}),
ThemeSharedModule.forRoot(),
OAuthModule.forRoot(),
NgxsModule.forRoot([]),
AccountConfigModule.forRoot({ redirectUrl: '/' }),
IdentityConfigModule,
TenantManagementConfigModule,
@ -35,10 +39,7 @@ import { SettingManagementConfigModule } from '@abp/ng.setting-management.config
AppRoutingModule,
SharedModule,
OAuthModule.forRoot(),
NgxsModule.forRoot([]),
NgxsReduxDevtoolsPluginModule.forRoot(),
...(environment.production ? [] : LOGGERS),
],
bootstrap: [AppComponent],
})

@ -1,15 +1,19 @@
<div class="card">
<div class="card-header">{{ '::Welcome' | abpLocalization }}</div>
<div class="card-body">
<p>
{{ '::LongWelcomeMessage' | abpLocalization }}
</p>
<p *ngIf="!hasLoggedIn">
<a routerLink="/account/login" [state]="{ redirectUrl: '/' }" class="btn btn-primary" role="button"
><i class="fa fa-sign-in mr-1"></i>{{ 'AbpIdentity::Login' | abpLocalization }}</a
>
</p>
<hr />
<p class="text-right"><a href="https://abp.io?ref=tmpl" target="_blank">abp.io</a></p>
<div id="AbpContentToolbar"></div>
<div class="jumbotron text-center">
<h1>{{ '::Welcome' | abpLocalization }}</h1>
<div class="row">
<div class="col-md-6 mx-auto">
<p>{{ '::LongWelcomeMessage' | abpLocalization }}</p>
<hr class="my-4" />
</div>
</div>
<a href="https://abp.io?ref=tmpl" target="_blank" class="btn btn-primary px-4">abp.io</a>
<a
*ngIf="!hasLoggedIn"
routerLink="/account/login"
[state]="{ redirectUrl: '/' }"
class="px-4 btn btn-primary ml-1"
role="button"
><i class="fa fa-sign-in"></i> {{ 'AbpIdentity::Login' | abpLocalization }}</a
>
</div>

@ -2,7 +2,7 @@ import { Component } from '@angular/core';
import { OAuthService } from 'angular-oauth2-oidc';
@Component({
selector: 'app-home',
selector: 'abp-home',
templateUrl: './home.component.html',
})
export class HomeComponent {

@ -7,7 +7,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="icon" type="image/x-icon" href="favicon.ico" />
</head>
<body>
<body class="abp-application-layout bg-light">
<app-root></app-root>
</body>
</html>

@ -10,14 +10,14 @@
"scripts:build": "cd scripts && npm install && npm run build"
},
"devDependencies": {
"@abp/ng.account.config": "^1.0.1",
"@abp/ng.core": "^1.0.1",
"@abp/ng.feature-management": "^1.0.1",
"@abp/ng.identity.config": "^1.0.1",
"@abp/ng.permission-management": "^1.0.1",
"@abp/ng.setting-management.config": "^1.0.1",
"@abp/ng.tenant-management.config": "^1.0.1",
"@abp/ng.theme.shared": "^1.0.1",
"@abp/ng.account.config": "^1.0.2",
"@abp/ng.core": "^1.0.2",
"@abp/ng.feature-management": "^1.0.2",
"@abp/ng.identity.config": "^1.0.2",
"@abp/ng.permission-management": "^1.0.2",
"@abp/ng.setting-management.config": "^1.0.2",
"@abp/ng.tenant-management.config": "^1.0.2",
"@abp/ng.theme.shared": "^1.0.2",
"@angular-builders/jest": "^8.2.0",
"@angular-devkit/build-angular": "~0.803.6",
"@angular-devkit/build-ng-packagr": "~0.803.6",
@ -38,6 +38,7 @@
"@ngneat/spectator": "^4.4.0",
"@ngx-validate/core": "^0.0.7",
"@ngxs/devtools-plugin": "^3.5.1",
"@ngxs/logger-plugin": "^3.5.1",
"@ngxs/router-plugin": "^3.5.0",
"@ngxs/storage-plugin": "^3.5.0",
"@ngxs/store": "^3.5.0",

@ -13,9 +13,11 @@ import { RegisterComponent } from './components/register/register.component';
import { TenantBoxComponent } from './components/tenant-box/tenant-box.component';
import { Options } from './models/options';
import { ACCOUNT_OPTIONS, optionsFactory } from './tokens/options.token';
import { AuthWrapperComponent } from './components/auth-wrapper/auth-wrapper.component';
@NgModule({
declarations: [
AuthWrapperComponent,
LoginComponent,
RegisterComponent,
TenantBoxComponent,

@ -0,0 +1,14 @@
<div class="row">
<div class="mx-auto col col-md-5">
<abp-tenant-box></abp-tenant-box>
<div class="abp-account-container">
<div class="card mt-3 shadow-sm rounded">
<div class="card-body p-5">
<ng-content *ngTemplateOutlet="mainContentRef"></ng-content>
</div>
<ng-content *ngTemplateOutlet="cancelContentRef"></ng-content>
</div>
</div>
</div>
</div>

@ -0,0 +1,13 @@
import { Component, Input, TemplateRef } from '@angular/core';
@Component({
selector: 'abp-auth-wrapper',
templateUrl: './auth-wrapper.component.html',
})
export class AuthWrapperComponent {
@Input()
mainContentRef: TemplateRef<any>;
@Input()
cancelContentRef: TemplateRef<any>;
}

@ -13,7 +13,11 @@
><span> * </span
><input type="password" id="confirm-new-password" class="form-control" formControlName="repeatNewPassword" />
</div>
<abp-button iconClass="fa fa-check" buttonClass="btn btn-primary color-white" buttonType="submit">{{
'AbpIdentity::Save' | abpLocalization
}}</abp-button>
<abp-button
iconClass="fa fa-check"
buttonClass="btn btn-primary color-white"
buttonType="submit"
[loading]="inProgress"
>{{ 'AbpIdentity::Save' | abpLocalization }}</abp-button
>
</form>

@ -5,6 +5,7 @@ import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { comparePasswords, Validation } from '@ngx-validate/core';
import { Store } from '@ngxs/store';
import snq from 'snq';
import { finalize } from 'rxjs/operators';
const { minLength, required } = Validators;
@ -17,11 +18,13 @@ const PASSWORD_FIELDS = ['newPassword', 'repeatNewPassword'];
export class ChangePasswordComponent implements OnInit {
form: FormGroup;
inProgress: boolean;
mapErrorsFn: Validation.MapErrorsFn = (errors, groupErrors, control) => {
if (PASSWORD_FIELDS.indexOf(control.name) < 0) return errors;
return errors.concat(groupErrors.filter(({ key }) => key === 'passwordMismatch'));
}
};
constructor(private fb: FormBuilder, private store: Store, private toasterService: ToasterService) {}
@ -40,7 +43,7 @@ export class ChangePasswordComponent implements OnInit {
onSubmit() {
if (this.form.invalid) return;
this.inProgress = true;
this.store
.dispatch(
new ChangePassword({
@ -48,6 +51,7 @@ export class ChangePasswordComponent implements OnInit {
newPassword: this.form.get('newPassword').value,
}),
)
.pipe(finalize(() => (this.inProgress = false)))
.subscribe({
next: () => {
this.form.reset();

@ -1,41 +1,50 @@
<div class="row">
<div class="col col-md-4 offset-md-4">
<abp-tenant-box></abp-tenant-box>
<div class="abp-account-container">
<h2>{{ 'AbpAccount::Login' | abpLocalization }}</h2>
<form [formGroup]="form" (ngSubmit)="onSubmit()" novalidate>
<div class="form-group">
<label for="login-input-user-name-or-email-address">{{
'AbpAccount::UserNameOrEmailAddress' | abpLocalization
}}</label>
<input
class="form-control"
type="text"
id="login-input-user-name-or-email-address"
formControlName="username"
autofocus
/>
</div>
<div class="form-group">
<label for="login-input-password">{{ 'AbpAccount::Password' | abpLocalization }}</label>
<input class="form-control" type="password" id="login-input-password" formControlName="password" />
</div>
<div class="form-check" validationTarget validationStyle>
<label class="form-check-label" for="login-input-remember-me">
<input class="form-check-input" type="checkbox" id="login-input-remember-me" formControlName="remember" />
{{ 'AbpAccount::RememberMe' | abpLocalization }}
</label>
</div>
<div class="mt-2">
<abp-button [loading]="inProgress" type="submit">
{{ 'AbpAccount::Login' | abpLocalization }}
</abp-button>
</div>
</form>
<div style="padding-top: 20px">
<a routerLink="/account/register">{{ 'AbpAccount::Register' | abpLocalization }}</a>
<abp-auth-wrapper [mainContentRef]="mainContentRef" [cancelContentRef]="cancelContentRef">
<ng-template #mainContentRef>
<h4>{{ 'AbpAccount::Login' | abpLocalization }}</h4>
<strong>
{{ 'AbpAccount::AreYouANewUser' | abpLocalization }}
<a class="text-decoration-none" routerLink="/account/register">{{ 'AbpAccount::Register' | abpLocalization }}</a>
</strong>
<form [formGroup]="form" (ngSubmit)="onSubmit()" novalidate class="mt-4">
<div class="form-group">
<label for="login-input-user-name-or-email-address">{{
'AbpAccount::UserNameOrEmailAddress' | abpLocalization
}}</label>
<input
class="form-control"
type="text"
id="login-input-user-name-or-email-address"
formControlName="username"
autofocus
/>
</div>
<div class="form-group">
<label for="login-input-password">{{ 'AbpAccount::Password' | abpLocalization }}</label>
<input class="form-control" type="password" id="login-input-password" formControlName="password" />
</div>
<div class="form-check" validationTarget validationStyle>
<label class="form-check-label" for="login-input-remember-me">
<input class="form-check-input" type="checkbox" id="login-input-remember-me" formControlName="remember" />
{{ 'AbpAccount::RememberMe' | abpLocalization }}
</label>
</div>
<abp-button
[loading]="inProgress"
buttonType="submit"
name="Action"
buttonClass="btn-block btn-lg mt-3 btn btn-primary"
>
{{ 'AbpAccount::Login' | abpLocalization }}
</abp-button>
</form>
</ng-template>
<ng-template #cancelContentRef>
<div class="card-footer text-center border-0">
<a routerLink="/">
<button type="button" name="Action" value="Cancel" class="px-2 py-0 btn btn-link">
{{ 'AbpAccount::Cancel' | abpLocalization }}
</button>
</a>
</div>
</div>
</div>
</ng-template>
</abp-auth-wrapper>

@ -1,34 +1,40 @@
<div class="row entry-row">
<div class="col-auto"></div>
<div id="breadcrumb" class="col-md-auto pl-md-0"></div>
<div class="col"></div>
</div>
<div id="AbpContentToolbar"></div>
<div id="ManageProfileWrapper">
<div class="row">
<div class="col-3">
<ul class="nav flex-column nav-pills" id="nav-tab" role="tablist">
<li class="nav-item pointer" (click)="selectedTab = 0">
<a class="nav-link" [ngClass]="{ active: selectedTab === 0 }" role="tab">{{
'AbpUi::ChangePassword' | abpLocalization
}}</a>
</li>
<li class="nav-item pointer" (click)="selectedTab = 1">
<a class="nav-link" [ngClass]="{ active: selectedTab === 1 }" role="tab">{{
'AbpAccount::PersonalSettings' | abpLocalization
}}</a>
</li>
</ul>
</div>
<div class="col-9">
<div class="tab-content" *ngIf="selectedTab === 0" [@fadeIn]>
<div class="tab-pane active" role="tabpanel">
<abp-change-password-form></abp-change-password-form>
</div>
<div class="card border-0 shadow-sm">
<div class="card-body">
<div class="row">
<div class="col-3">
<ul class="nav flex-column nav-pills" id="nav-tab" role="tablist">
<li class="nav-item" (click)="selectedTab = 0">
<a class="nav-link" [ngClass]="{ active: selectedTab === 0 }" role="tab" href="javascript:void(0)">{{
'AbpUi::ChangePassword' | abpLocalization
}}</a>
</li>
<li class="nav-item" (click)="selectedTab = 1">
<a class="nav-link" [ngClass]="{ active: selectedTab === 1 }" role="tab" href="javascript:void(0)">{{
'AbpAccount::PersonalSettings' | abpLocalization
}}</a>
</li>
</ul>
</div>
<div class="tab-content" *ngIf="selectedTab === 1" [@fadeIn]>
<div class="tab-pane active" role="tabpanel">
<abp-personal-settings-form></abp-personal-settings-form>
<div class="col-9">
<div class="tab-content" *ngIf="selectedTab === 0" [@fadeIn]>
<div class="tab-pane active" role="tabpanel">
<h4>
{{ 'AbpIdentity::ChangePassword' | abpLocalization }}
<hr />
</h4>
<abp-change-password-form></abp-change-password-form>
</div>
</div>
<div class="tab-content" *ngIf="selectedTab === 1" [@fadeIn]>
<div class="tab-pane active" role="tabpanel">
<h4>
{{ 'AbpIdentity::PersonalSettings' | abpLocalization }}
<hr />
</h4>
<abp-personal-settings-form></abp-personal-settings-form>
</div>
</div>
</div>
</div>

@ -25,7 +25,12 @@
<label for="phone-number">{{ 'AbpIdentity::DisplayName:PhoneNumber' | abpLocalization }}</label
><input type="text" id="phone-number" class="form-control" formControlName="phoneNumber" />
</div>
<abp-button buttonType="submit" iconClass="fa fa-check" buttonClass="btn btn-primary color-white">
<abp-button
buttonType="submit"
iconClass="fa fa-check"
buttonClass="btn btn-primary color-white"
[loading]="inProgress"
>
{{ 'AbpIdentity::Save' | abpLocalization }}</abp-button
>
</form>

@ -3,7 +3,7 @@ import { Component, OnInit } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { Select, Store } from '@ngxs/store';
import { Observable } from 'rxjs';
import { take, withLatestFrom } from 'rxjs/operators';
import { take, withLatestFrom, finalize } from 'rxjs/operators';
import { ToasterService } from '@abp/ng.theme.shared';
const { maxLength, required, email } = Validators;
@ -18,8 +18,14 @@ export class PersonalSettingsComponent implements OnInit {
form: FormGroup;
inProgress: boolean;
constructor(private fb: FormBuilder, private store: Store, private toasterService: ToasterService) {}
ngOnInit() {
this.buildForm();
}
buildForm() {
this.store
.dispatch(new GetProfile())
@ -40,13 +46,12 @@ export class PersonalSettingsComponent implements OnInit {
submit() {
if (this.form.invalid) return;
this.store.dispatch(new UpdateProfile(this.form.value)).subscribe(() => {
this.toasterService.success('AbpAccount::PersonalSettingsSaved', 'Success', { life: 5000 });
});
}
ngOnInit() {
this.buildForm();
this.inProgress = true;
this.store
.dispatch(new UpdateProfile(this.form.value))
.pipe(finalize(() => (this.inProgress = false)))
.subscribe(() => {
this.toasterService.success('AbpAccount::PersonalSettingsSaved', 'Success', { life: 5000 });
});
}
}

@ -1,30 +1,32 @@
<div class="row">
<div class="col col-md-4 offset-md-4">
<abp-tenant-box></abp-tenant-box>
<div class="abp-account-container">
<h2>{{ 'AbpAccount::Register' | abpLocalization }}</h2>
<form [formGroup]="form" (ngSubmit)="onSubmit()" novalidate>
<div class="form-group">
<label for="input-user-name">{{ 'AbpAccount::UserName' | abpLocalization }}</label
><span> * </span
><input autofocus type="text" id="input-user-name" class="form-control" formControlName="username" />
</div>
<div class="form-group">
<label for="input-email-address">{{ 'AbpAccount::EmailAddress' | abpLocalization }}</label
><span> * </span><input type="email" id="input-email-address" class="form-control" formControlName="email" />
</div>
<div class="form-group">
<label for="input-password">{{ 'AbpAccount::Password' | abpLocalization }}</label
><span> * </span><input type="password" id="input-password" class="form-control" formControlName="password" />
</div>
<abp-button [loading]="inProgress" type="submit">
{{ 'AbpAccount::Register' | abpLocalization }}
</abp-button>
</form>
<div style="padding-top: 20px">
<a routerLink="/account/login">{{ 'AbpAccount::Login' | abpLocalization }}</a>
<abp-auth-wrapper [mainContentRef]="mainContentRef">
<ng-template #mainContentRef>
<h4>{{ 'AbpAccount::Register' | abpLocalization }}</h4>
<strong>
{{ 'AbpAccount::AlreadyRegistered' | abpLocalization }}
<a class="text-decoration-none" routerLink="/account/login">{{ 'AbpAccount::Login' | abpLocalization }}</a>
</strong>
<form [formGroup]="form" (ngSubmit)="onSubmit()" novalidate class="mt-4">
<div class="form-group">
<label for="input-user-name">{{ 'AbpAccount::UserName' | abpLocalization }}</label
><span> * </span
><input autofocus type="text" id="input-user-name" class="form-control" formControlName="username" />
</div>
</div>
</div>
</div>
<div class="form-group">
<label for="input-email-address">{{ 'AbpAccount::EmailAddress' | abpLocalization }}</label
><span> * </span><input type="email" id="input-email-address" class="form-control" formControlName="email" />
</div>
<div class="form-group">
<label for="input-password">{{ 'AbpAccount::Password' | abpLocalization }}</label
><span> * </span><input type="password" id="input-password" class="form-control" formControlName="password" />
</div>
<abp-button
[loading]="inProgress"
buttonType="submit"
name="Action"
buttonClass="btn-block btn-lg mt-3 btn btn-primary"
>
{{ 'AbpAccount::Register' | abpLocalization }}
</abp-button>
</form>
</ng-template>
</abp-auth-wrapper>

@ -1,15 +1,28 @@
<div
class="tenant-switch-box"
style="background-color: #eee; margin-bottom: 20px; color: #000; padding: 10px; text-align: center;"
>
<span style="color: #666;">{{ 'AbpUiMultiTenancy::Tenant' | abpLocalization }}: </span>
<strong>
<i>{{ tenantName || ('AbpUiMultiTenancy::NotSelected' | abpLocalization) }}</i>
</strong>
(<a id="abp-tenant-switch-link" style="color: #333; cursor: pointer" (click)="onSwitch()">{{
'AbpUiMultiTenancy::Switch' | abpLocalization
}}</a
>)
<div class="card shadow-sm rounded mb-3">
<div class="card-body px-5">
<div class="row">
<div class="col">
<span style="font-size: 0.8em;" class="text-uppercase text-muted">{{
'AbpUiMultiTenancy::Tenant' | abpLocalization
}}</span
><br />
<h6 class="m-0 d-inline-block">
<span>
{{ tenantName || ('AbpUiMultiTenancy::NotSelected' | abpLocalization) }}
</span>
</h6>
</div>
<div class="col-auto">
<a
id="AbpTenantSwitchLink"
href="javascript:void(0);"
class="btn btn-sm mt-3 btn-outline-primary"
(click)="onSwitch()"
>{{ 'AbpUiMultiTenancy::Switch' | abpLocalization }}</a
>
</div>
</div>
</div>
</div>
<abp-modal [(visible)]="isModalVisible" size="md">

@ -14,6 +14,7 @@ export class IdentityConfigService {
path: '',
order: 1,
wrapper: true,
iconClass: 'fa fa-wrench',
},
{
name: 'AbpIdentity::Menu:IdentityManagement',
@ -23,8 +24,8 @@ export class IdentityConfigService {
layout: eLayoutType.application,
iconClass: 'fa fa-id-card-o',
children: [
{ path: 'roles', name: 'AbpIdentity::Roles', order: 2, requiredPolicy: 'AbpIdentity.Roles' },
{ path: 'users', name: 'AbpIdentity::Users', order: 1, requiredPolicy: 'AbpIdentity.Users' },
{ path: 'roles', name: 'AbpIdentity::Roles', order: 1, requiredPolicy: 'AbpIdentity.Roles' },
{ path: 'users', name: 'AbpIdentity::Users', order: 2, requiredPolicy: 'AbpIdentity.Users' },
],
},
]);

@ -19,15 +19,6 @@
<div id="identity-roles-wrapper" class="card">
<div class="card-body">
<div id="data-tables-table-filter" class="data-tables-filter">
<label
><input
type="search"
class="form-control form-control-sm"
[placeholder]="'AbpUi::PagerSearch' | abpLocalization"
(input.debounce)="onSearch($event.target.value)"
/></label>
</div>
<p-table
*ngIf="[150, 0] as columnWidths"
[value]="data$ | async"
@ -82,11 +73,7 @@
<i class="fa fa-cog mr-1"></i>{{ 'AbpIdentity::Actions' | abpLocalization }}
</button>
<div ngbDropdownMenu>
<button
[abpPermission]="'AbpIdentity.Roles.Update'"
ngbDropdownItem
(click)="onEdit(data.id)"
>
<button [abpPermission]="'AbpIdentity.Roles.Update'" ngbDropdownItem (click)="onEdit(data.id)">
{{ 'AbpIdentity::Edit' | abpLocalization }}
</button>
<button
@ -98,7 +85,8 @@
</button>
<button
[abpPermission]="'AbpIdentity.Roles.Delete'"
ngbDropdownItem (click)="delete(data.id, data.name)"
ngbDropdownItem
(click)="delete(data.id, data.name)"
>
{{ 'AbpIdentity::Delete' | abpLocalization }}
</button>

@ -49,11 +49,6 @@ export class RolesComponent implements OnInit {
this.get();
}
onSearch(value) {
this.pageQuery.filter = value;
this.get();
}
createForm() {
this.form = this.fb.group({
name: new FormControl({ value: this.selected.name || '', disabled: this.selected.isStatic }, [

@ -3,7 +3,11 @@ import { eLayoutType } from '@abp/ng.core';
@Component({
selector: 'abp-layout-account',
templateUrl: './account-layout.component.html',
template: `
<router-outlet></router-outlet>
<abp-confirmation></abp-confirmation>
<abp-toast></abp-toast>
`,
})
export class AccountLayoutComponent {
// required for dynamic component

@ -1,124 +1,197 @@
<abp-layout>
<ul class="navbar-nav mr-auto">
<ng-container
*ngFor="let route of visibleRoutes$ | async; trackBy: trackByFn"
[ngTemplateOutlet]="route?.children?.length ? dropdownLink : defaultLink"
[ngTemplateOutletContext]="{ $implicit: route }"
<nav
class="navbar navbar-expand-md navbar-dark bg-dark shadow-sm flex-column flex-md-row mb-4"
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" />
</a>
<button
class="navbar-toggler"
type="button"
[attr.aria-expanded]="!isCollapsed"
(click)="isCollapsed = !isCollapsed"
>
</ng-container>
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="main-navbar-collapse" [ngbCollapse]="isCollapsed">
<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]">{{ route.name | abpLocalization }}</a>
</li>
</ng-template>
<ng-template #defaultLink let-route>
<li class="nav-item" [abpPermission]="route.requiredPolicy">
<a class="nav-link" [routerLink]="[route.url]">{{ route.name | abpLocalization }}</a>
</li>
</ng-template>
<ng-template #dropdownLink let-route>
<li
#navbarRootDropdown
ngbDropdown
[abpPermission]="route.requiredPolicy"
[abpVisibility]="routeContainer"
class="nav-item dropdown pointer"
display="static"
>
<a ngbDropdownToggle class="nav-link dropdown-toggle pointer" data-toggle="dropdown">
{{ route.name | abpLocalization }}
</a>
<div #routeContainer ngbDropdownMenu class="dropdown-menu dropdown-menu-right">
<ng-template #dropdownLink let-route>
<li
#navbarRootDropdown
ngbDropdown
[abpPermission]="route.requiredPolicy"
[abpVisibility]="routeContainer"
class="nav-item"
display="static"
(click)="$event.preventDefault(); $event.stopPropagation()"
>
<a
ngbDropdownToggle
class="nav-link"
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 ngbDropdownMenu class="dropdown-menu border-0 shadow-sm">
<ng-template
#forTemplate
ngFor
[ngForOf]="route.children"
[ngForTrackBy]="trackByFn"
[ngForTemplate]="childWrapper"
></ng-template>
</div>
</li>
</ng-template>
<ng-template #childWrapper let-child>
<ng-template
#forTemplate
ngFor
[ngForOf]="route.children"
[ngForTrackBy]="trackByFn"
[ngForTemplate]="childWrapper"
[ngTemplateOutlet]="child?.children?.length ? dropdownChild : defaultChild"
[ngTemplateOutletContext]="{ $implicit: child }"
></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>
<ng-template #defaultChild let-child>
<div class="dropdown-submenu" [abpPermission]="child.requiredPolicy">
<a class="dropdown-item py-2 px-2" [routerLink]="[child.url]">
<i *ngIf="child.iconClass" [ngClass]="child.iconClass"></i>
{{ child.name | abpLocalization }}</a
>
</div>
</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 pointer"
ngbDropdown
[display]="isDropdownChildDynamic ? 'dynamic' : 'static'"
placement="right-top"
[abpPermission]="child.requiredPolicy"
>
<div ngbDropdownToggle [class.dropdown-toggle]="false" class="pointer">
<a
abpEllipsis="210px"
[abpEllipsisEnabled]="isDropdownChildDynamic"
role="button"
class="btn d-block text-left py-2 px-2 dropdown-toggle"
<ng-template #dropdownChild let-child>
<div
[abpVisibility]="childrenContainer"
class="dropdown-submenu"
ngbDropdown
[display]="isDropdownChildDynamic ? 'dynamic' : 'static'"
placement="right-top"
[autoClose]="true"
[abpPermission]="child.requiredPolicy"
(openChange)="openChange($event, childrenContainer)"
>
<i *ngIf="child.iconClass" [ngClass]="child.iconClass"></i>
{{ child.name | abpLocalization }}
</a>
</div>
<div #childrenContainer ngbDropdownMenu class="dropdown-menu dropdown-menu-right">
<ng-template
ngFor
[ngForOf]="child.children"
[ngForTrackBy]="trackByFn"
[ngForTemplate]="childWrapper"
></ng-template>
</div>
</div>
</ng-template>
</ul>
<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 ngbDropdownMenu class="dropdown-menu border-0 shadow-sm">
<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>
</div>
</div>
</nav>
<ul class="navbar-nav ml-auto">
<ng-container
*ngFor="let element of rightPartElements; trackBy: trackElementByFn"
[ngTemplateOutlet]="element"
></ng-container>
</ul>
</abp-layout>
<div [@slideFromBottom]="outlet && outlet.activatedRoute && outlet.activatedRoute.routeConfig.path" class="container">
<router-outlet #outlet="outlet"></router-outlet>
</div>
<abp-confirmation></abp-confirmation>
<abp-toast></abp-toast>
<ng-template #appName>
{{ appInfo.name }}
</ng-template>
<ng-template #language>
<li class="nav-item dropdown pointer" ngbDropdown>
<a ngbDropdownToggle class="nav-link dropdown-toggle text-white pointer" data-toggle="dropdown">
{{ defaultLanguage$ | async }}
</a>
<div ngbDropdownMenu class="dropdown-menu dropdown-menu-right">
<li class="nav-item">
<div ngbDropdown display="static">
<a
*ngFor="let lang of dropdownLanguages$ | async"
class="dropdown-item"
(click)="onChangeLang(lang.cultureName)"
>{{ lang?.displayName }}</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
ngbDropdownMenu
class="dropdown-menu dropdown-menu-right border-0 shadow-sm"
aria-labelledby="dropdownMenuLink"
>
<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 *ngIf="(currentUser$ | async)?.isAuthenticated" class="nav-item dropdown pointer" ngbDropdown>
<a ngbDropdownToggle class="nav-link dropdown-toggle text-white pointer" data-toggle="dropdown">
{{ (currentUser$ | async)?.userName }}
</a>
<div ngbDropdownMenu class="dropdown-menu dropdown-menu-right">
<a class="dropdown-item pointer" routerLink="/account/manage-profile">{{
'AbpAccount::ManageYourProfile' | abpLocalization
}}</a>
<a class="dropdown-item pointer" (click)="logout()">{{ 'AbpUi::Logout' | abpLocalization }}</a>
<li *ngIf="(currentUser$ | async)?.isAuthenticated" class="nav-item">
<div 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
ngbDropdownMenu
class="dropdown-menu dropdown-menu-right border-0 shadow-sm"
aria-labelledby="dropdownMenuLink"
>
<a class="dropdown-item" routerLink="/account/manage-profile">{{
'AbpAccount::ManageYourProfile' | abpLocalization
}}</a>
<a class="dropdown-item" href="javascript:void(0)" (click)="logout()">{{
'AbpUi::Logout' | abpLocalization
}}</a>
</div>
</div>
</li>
</ng-template>

@ -7,6 +7,7 @@ import {
SetLanguage,
SessionState,
takeUntilDestroy,
Config,
} from '@abp/ng.core';
import {
AfterViewInit,
@ -17,6 +18,7 @@ import {
TrackByFunction,
ViewChild,
ViewChildren,
Renderer2,
} from '@angular/core';
import { NgbDropdown } from '@ng-bootstrap/ng-bootstrap';
import { Navigate, RouterState } from '@ngxs/router-plugin';
@ -29,10 +31,12 @@ import snq from 'snq';
import { AddNavigationElement } from '../../actions';
import { Layout } from '../../models/layout';
import { LayoutState } from '../../states';
import { slideFromBottom } from '@abp/ng.theme.shared';
@Component({
selector: 'abp-layout-application',
templateUrl: './application-layout.component.html',
animations: [slideFromBottom],
})
export class ApplicationLayoutComponent implements AfterViewInit, OnDestroy {
// required for dynamic component
@ -61,6 +65,12 @@ export class ApplicationLayoutComponent implements AfterViewInit, OnDestroy {
isDropdownChildDynamic: boolean;
isCollapsed = true;
get appInfo(): Config.Application {
return this.store.selectSnapshot(ConfigState.getApplicationInfo);
}
get visibleRoutes$(): Observable<ABP.FullRoute[]> {
return this.routes$.pipe(map(routes => getVisibleRoutes(routes)));
}
@ -90,7 +100,7 @@ export class ApplicationLayoutComponent implements AfterViewInit, OnDestroy {
trackElementByFn: TrackByFunction<ABP.FullRoute> = (_, element) => element;
constructor(private store: Store, private oauthService: OAuthService) {}
constructor(private store: Store, private oauthService: OAuthService, private renderer: Renderer2) {}
private checkWindowWidth() {
setTimeout(() => {
@ -154,6 +164,17 @@ export class ApplicationLayoutComponent implements AfterViewInit, OnDestroy {
);
this.store.dispatch(new GetAppConfiguration());
}
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[]) {

@ -4,8 +4,9 @@ import { eLayoutType } from '@abp/ng.core';
@Component({
selector: 'abp-layout-empty',
template: `
Layout-empty
<router-outlet></router-outlet>
<abp-confirmation></abp-confirmation>
<abp-toast></abp-toast>
`,
})
export class EmptyLayoutComponent {

@ -1,26 +0,0 @@
<nav class="navbar navbar-expand-md navbar-dark bg-dark fixed-top" id="main-navbar">
<a class="navbar-brand" routerLink="/">
<img *ngIf="appInfo.logoUrl; else appName" [src]="appInfo.logoUrl" [alt]="appInfo.name" />
</a>
<button class="navbar-toggler" type="button" [attr.aria-expanded]="!isCollapsed" (click)="isCollapsed = !isCollapsed">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="main-navbar-collapse" [ngbCollapse]="isCollapsed">
<ng-content></ng-content>
</div>
</nav>
<div
[@slideFromBottom]="outlet && outlet.activatedRoute && outlet.activatedRoute.routeConfig.path"
style="padding-top: 5rem;"
class="container"
>
<router-outlet #outlet="outlet"></router-outlet>
</div>
<abp-confirmation></abp-confirmation>
<abp-toast></abp-toast>
<ng-template #appName>
{{ appInfo.name }}
</ng-template>

@ -1,19 +0,0 @@
import { Config, ConfigState } from '@abp/ng.core';
import { slideFromBottom } from '@abp/ng.theme.shared';
import { Component } from '@angular/core';
import { Store } from '@ngxs/store';
@Component({
selector: ' abp-layout',
templateUrl: './layout.component.html',
animations: [slideFromBottom]
})
export class LayoutComponent {
isCollapsed = true;
get appInfo(): Config.Application {
return this.store.selectSnapshot(ConfigState.getApplicationInfo);
}
constructor(private store: Store) {}
}

@ -6,4 +6,63 @@ export default `
.entry-row {
margin-bottom: 15px;
}
#main-navbar-tools a.dropdown-toggle {
text-decoration: none;
color: #fff;
}
.navbar .dropdown-submenu {
position: relative;
}
.navbar .dropdown-menu {
margin: 0;
padding: 0;
}
.navbar .dropdown-menu a {
font-size: .9em;
padding: 10px 15px;
display: block;
min-width: 210px;
text-align: left;
border-radius: 0.25rem;
min-height: 44px;
}
.navbar .dropdown-submenu a::after {
transform: rotate(-90deg);
position: absolute;
right: 16px;
top: 18px;
}
.navbar .dropdown-submenu .dropdown-menu {
top: 0;
left: 100%;
}
.card-header .btn {
padding: 2px 6px;
}
.card-header h5 {
margin: 0;
}
.container > .card {
box-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075) !important;
}
@media screen and (min-width: 768px) {
.navbar .dropdown:hover > .dropdown-menu {
display: block;
}
.navbar .dropdown-submenu:hover > .dropdown-menu {
display: block;
}
}
.input-validation-error {
border-color: #dc3545;
}
.field-validation-error {
font-size: 0.8em;
}
`;

@ -8,7 +8,6 @@ import { ToastModule } from 'primeng/toast';
import { AccountLayoutComponent } from './components/account-layout/account-layout.component';
import { ApplicationLayoutComponent } from './components/application-layout/application-layout.component';
import { EmptyLayoutComponent } from './components/empty-layout/empty-layout.component';
import { LayoutComponent } from './components/layout/layout.component';
import { LayoutState } from './states/layout.state';
import { ValidationErrorComponent } from './components/validation-error/validation-error.component';
import { InitialService } from './services/initial.service';
@ -16,7 +15,7 @@ import { InitialService } from './services/initial.service';
export const LAYOUTS = [ApplicationLayoutComponent, AccountLayoutComponent, EmptyLayoutComponent];
@NgModule({
declarations: [...LAYOUTS, LayoutComponent, ValidationErrorComponent],
declarations: [...LAYOUTS, ValidationErrorComponent],
imports: [
CoreModule,
ThemeSharedModule,
@ -34,13 +33,13 @@ export const LAYOUTS = [ApplicationLayoutComponent, AccountLayoutComponent, Empt
min: 'AbpAccount::ThisFieldMustBeBetween{0}And{1}[{{ min }},{{ max }}]',
minlength: 'AbpAccount::ThisFieldMustBeAStringOrArrayTypeWithAMinimumLengthOf[{{ min }},{{ max }}]',
required: 'AbpAccount::ThisFieldIsRequired.',
passwordMismatch: 'AbpIdentity::Identity.PasswordConfirmationFailed'
passwordMismatch: 'AbpIdentity::Identity.PasswordConfirmationFailed',
},
errorTemplate: ValidationErrorComponent
})
errorTemplate: ValidationErrorComponent,
}),
],
exports: [...LAYOUTS],
entryComponents: [...LAYOUTS, ValidationErrorComponent]
entryComponents: [...LAYOUTS, ValidationErrorComponent],
})
export class ThemeBasicModule {
constructor(private initialService: InitialService) {}

@ -1,15 +1,19 @@
<div class="card">
<div class="card-header">{{ '::Welcome' | abpLocalization }}</div>
<div class="card-body">
<p>
{{ '::LongWelcomeMessage' | abpLocalization }}
</p>
<p *ngIf="!hasLoggedIn">
<a routerLink="/account/login" [state]="{ redirectUrl: '/' }" class="btn btn-primary" role="button"
><i class="fa fa-sign-in mr-1"></i>{{ 'AbpIdentity::Login' | abpLocalization }}</a
>
</p>
<hr />
<p class="text-right"><a href="https://abp.io?ref=tmpl" target="_blank">abp.io</a></p>
<div id="AbpContentToolbar"></div>
<div class="jumbotron text-center">
<h1>{{ '::Welcome' | abpLocalization }}</h1>
<div class="row">
<div class="col-md-6 mx-auto">
<p>{{ '::LongWelcomeMessage' | abpLocalization }}</p>
<hr class="my-4" />
</div>
</div>
<a href="https://abp.io?ref=tmpl" target="_blank" class="btn btn-primary px-4">abp.io</a>
<a
*ngIf="!hasLoggedIn"
routerLink="/account/login"
[state]="{ redirectUrl: '/' }"
class="px-4 btn btn-primary ml-1"
role="button"
><i class="fa fa-sign-in"></i> {{ 'AbpIdentity::Login' | abpLocalization }}</a
>
</div>

@ -8,7 +8,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="icon" type="image/x-icon" href="favicon.ico" />
</head>
<body>
<body class="abp-application-layout bg-light">
<app-root>
<div class="donut centered"></div>
</app-root>

Loading…
Cancel
Save