mirror of https://github.com/abpframework/abp.git
52 changed files with 1343 additions and 14 deletions
@ -1,7 +1,3 @@ |
|||
# core |
|||
<h1> @abp/ng.core </h1> |
|||
|
|||
This library was generated with [Nx](https://nx.dev). |
|||
|
|||
## Running unit tests |
|||
|
|||
Run `nx test core` to execute the unit tests. |
|||
[docs.abp.io](https://docs.abp.io) |
|||
|
|||
@ -0,0 +1,36 @@ |
|||
{ |
|||
"extends": ["../../.eslintrc.json"], |
|||
"ignorePatterns": ["!**/*"], |
|||
"overrides": [ |
|||
{ |
|||
"files": ["*.ts"], |
|||
"extends": [ |
|||
"plugin:@nrwl/nx/angular", |
|||
"plugin:@angular-eslint/template/process-inline-templates" |
|||
], |
|||
"rules": { |
|||
"@angular-eslint/directive-selector": [ |
|||
"error", |
|||
{ |
|||
"type": "attribute", |
|||
"prefix": "abp", |
|||
"style": "camelCase" |
|||
} |
|||
], |
|||
"@angular-eslint/component-selector": [ |
|||
"error", |
|||
{ |
|||
"type": "element", |
|||
"prefix": "abp", |
|||
"style": "kebab-case" |
|||
} |
|||
] |
|||
} |
|||
}, |
|||
{ |
|||
"files": ["*.html"], |
|||
"extends": ["plugin:@nrwl/nx/angular-template"], |
|||
"rules": {} |
|||
} |
|||
] |
|||
} |
|||
@ -0,0 +1,3 @@ |
|||
<h1> @abp/ng.theme.basic </h1> |
|||
|
|||
[docs.abp.io](https://docs.abp.io) |
|||
@ -0,0 +1,20 @@ |
|||
module.exports = { |
|||
displayName: 'theme-basic', |
|||
preset: '../../jest.preset.js', |
|||
setupFilesAfterEnv: ['<rootDir>/src/test-setup.ts'], |
|||
globals: { |
|||
'ts-jest': { |
|||
tsconfig: '<rootDir>/tsconfig.spec.json', |
|||
stringifyContentPathRegex: '\\.(html|svg)$', |
|||
}, |
|||
}, |
|||
coverageDirectory: '../../coverage/libs/theme-basic', |
|||
transform: { |
|||
'^.+\\.(ts|js|html)$': 'jest-preset-angular', |
|||
}, |
|||
snapshotSerializers: [ |
|||
'jest-preset-angular/build/serializers/no-ng-attributes', |
|||
'jest-preset-angular/build/serializers/ng-snapshot', |
|||
'jest-preset-angular/build/serializers/html-comment', |
|||
], |
|||
}; |
|||
@ -0,0 +1,8 @@ |
|||
{ |
|||
"$schema": "../../node_modules/ng-packagr/ng-package.schema.json", |
|||
"dest": "../../dist/libs/theme-basic", |
|||
"lib": { |
|||
"entryFile": "src/public-api.ts" |
|||
}, |
|||
"allowedNonPeerDependencies": ["@abp/ng.theme.shared", "@abp/ng.account.core"] |
|||
} |
|||
@ -0,0 +1,17 @@ |
|||
{ |
|||
"name": "@abp/ng.theme.basic", |
|||
"version": "4.4.0", |
|||
"homepage": "https://abp.io", |
|||
"repository": { |
|||
"type": "git", |
|||
"url": "https://github.com/abpframework/abp.git" |
|||
}, |
|||
"dependencies": { |
|||
"@abp/ng.account.core": "~4.4.0", |
|||
"@abp/ng.theme.shared": "~4.4.0", |
|||
"tslib": "^2.0.0" |
|||
}, |
|||
"publishConfig": { |
|||
"access": "public" |
|||
} |
|||
} |
|||
@ -0,0 +1,62 @@ |
|||
<nav |
|||
class="navbar navbar-expand-lg navbar-dark bg-dark shadow-sm flex-column flex-md-row mb-4" |
|||
id="main-navbar" |
|||
style="min-height: 4rem" |
|||
> |
|||
<div class="container"> |
|||
<abp-logo *abpReplaceableTemplate="{ componentKey: service.logoComponentKey }"></abp-logo> |
|||
<button |
|||
class="navbar-toggler" |
|||
type="button" |
|||
[attr.aria-expanded]="!service.isCollapsed" |
|||
(click)="service.isCollapsed = !service.isCollapsed" |
|||
> |
|||
<span class="navbar-toggler-icon"></span> |
|||
</button> |
|||
<div |
|||
class="navbar-collapse" |
|||
[class.overflow-hidden]="service.smallScreen" |
|||
id="main-navbar-collapse" |
|||
> |
|||
<ng-container *ngTemplateOutlet="!service.smallScreen ? navigations : null"></ng-container> |
|||
|
|||
<div |
|||
*ngIf="service.smallScreen" |
|||
[@collapseWithMargin]="service.isCollapsed ? 'collapsed' : 'expanded'" |
|||
> |
|||
<ng-container *ngTemplateOutlet="navigations"></ng-container> |
|||
</div> |
|||
|
|||
<ng-template #navigations> |
|||
<abp-routes |
|||
*abpReplaceableTemplate="{ |
|||
componentKey: service.routesComponentKey, |
|||
inputs: { |
|||
smallScreen: { value: service.smallScreen } |
|||
} |
|||
}" |
|||
class="mx-auto" |
|||
[smallScreen]="service.smallScreen" |
|||
></abp-routes> |
|||
|
|||
<abp-nav-items |
|||
*abpReplaceableTemplate="{ |
|||
componentKey: service.navItemsComponentKey |
|||
}" |
|||
></abp-nav-items> |
|||
</ng-template> |
|||
</div> |
|||
</div> |
|||
</nav> |
|||
|
|||
<!-- [@slideFromBottom]="outlet.isActivated && outlet.activatedRoute?.routeConfig?.path" TODO: throws ExpressionChangedAfterItHasBeenCheck when animation is active. It should be fixed --> |
|||
<div class="container"> |
|||
<abp-page-alert-container></abp-page-alert-container> |
|||
<abp-auth-wrapper |
|||
*abpReplaceableTemplate="{ |
|||
componentKey: authWrapperKey |
|||
}" |
|||
> |
|||
<router-outlet #outlet="outlet"></router-outlet> |
|||
</abp-auth-wrapper> |
|||
</div> |
|||
@ -0,0 +1,21 @@ |
|||
import { AfterViewInit, Component } from '@angular/core'; |
|||
import { eLayoutType, SubscriptionService } from '@abp/ng.core'; |
|||
import { LayoutService } from '../../services/layout.service'; |
|||
|
|||
@Component({ |
|||
selector: 'abp-layout-account', |
|||
templateUrl: './account-layout.component.html', |
|||
providers: [LayoutService, SubscriptionService], |
|||
}) |
|||
export class AccountLayoutComponent implements AfterViewInit { |
|||
// required for dynamic component
|
|||
static type = eLayoutType.account; |
|||
|
|||
authWrapperKey = 'Account.AuthWrapperComponent'; |
|||
|
|||
constructor(public service: LayoutService) {} |
|||
|
|||
ngAfterViewInit() { |
|||
this.service.subscribeWindowSize(); |
|||
} |
|||
} |
|||
@ -0,0 +1,27 @@ |
|||
<div class="row"> |
|||
<div class="mx-auto col col-md-5"> |
|||
<ng-container *ngIf="(service.isMultiTenancyEnabled$ | async) && service.isTenantBoxVisible"> |
|||
<abp-tenant-box |
|||
*abpReplaceableTemplate="{ componentKey: service.tenantBoxKey }" |
|||
></abp-tenant-box> |
|||
</ng-container> |
|||
|
|||
<div class="abp-account-container"> |
|||
<div |
|||
*ngIf="service.enableLocalLogin$ | async; else disableLocalLoginTemplate" |
|||
class="card mt-3 shadow-sm rounded" |
|||
> |
|||
<div class="card-body p-5"> |
|||
<ng-content></ng-content> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
|
|||
<ng-template #disableLocalLoginTemplate> |
|||
<div class="alert alert-warning"> |
|||
<strong>{{ 'AbpAccount::InvalidLoginRequest' | abpLocalization }}</strong> |
|||
{{ 'AbpAccount::ThereAreNoLoginSchemesConfiguredForThisClient' | abpLocalization }} |
|||
</div> |
|||
</ng-template> |
|||
@ -0,0 +1,25 @@ |
|||
import { ComponentFixture, TestBed } from '@angular/core/testing'; |
|||
|
|||
import { AuthWrapperComponent } from './auth-wrapper.component'; |
|||
|
|||
describe('AuthWrapperComponent', () => { |
|||
let component: AuthWrapperComponent; |
|||
let fixture: ComponentFixture<AuthWrapperComponent>; |
|||
|
|||
beforeEach(async () => { |
|||
await TestBed.configureTestingModule({ |
|||
declarations: [ AuthWrapperComponent ] |
|||
}) |
|||
.compileComponents(); |
|||
}); |
|||
|
|||
beforeEach(() => { |
|||
fixture = TestBed.createComponent(AuthWrapperComponent); |
|||
component = fixture.componentInstance; |
|||
fixture.detectChanges(); |
|||
}); |
|||
|
|||
it('should create', () => { |
|||
expect(component).toBeTruthy(); |
|||
}); |
|||
}); |
|||
@ -0,0 +1,13 @@ |
|||
import { Component, OnInit } from '@angular/core'; |
|||
import { AuthWrapperService } from '@abp/ng.account.core'; |
|||
|
|||
@Component({ |
|||
selector: 'abp-auth-wrapper', |
|||
templateUrl: './auth-wrapper.component.html', |
|||
providers: [AuthWrapperService], |
|||
}) |
|||
export class AuthWrapperComponent implements OnInit { |
|||
constructor(public service: AuthWrapperService) {} |
|||
|
|||
ngOnInit(): void {} |
|||
} |
|||
@ -0,0 +1,63 @@ |
|||
<ng-container *ngIf="(service.currentTenant$ | async) || {} as currentTenant"> |
|||
<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"> |
|||
<i>{{ currentTenant.name || ('AbpUiMultiTenancy::NotSelected' | abpLocalization) }}</i> |
|||
</h6> |
|||
</div> |
|||
<div class="col-auto"> |
|||
<a |
|||
id="AbpTenantSwitchLink" |
|||
href="javascript:void(0);" |
|||
class="btn btn-sm mt-3 btn-outline-primary" |
|||
(click)="service.onSwitch()" |
|||
>{{ 'AbpUiMultiTenancy::Switch' | abpLocalization }}</a |
|||
> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
|
|||
<abp-modal size="md" [(visible)]="service.isModalVisible" [busy]="service.modalBusy"> |
|||
<ng-template #abpHeader> |
|||
<h5>Switch Tenant</h5> |
|||
</ng-template> |
|||
<ng-template #abpBody> |
|||
<form (ngSubmit)="service.save()"> |
|||
<div class="mt-2"> |
|||
<div class="form-group"> |
|||
<label for="name">{{ 'AbpUiMultiTenancy::Name' | abpLocalization }}</label> |
|||
<input |
|||
[(ngModel)]="service.name" |
|||
type="text" |
|||
id="name" |
|||
name="tenant" |
|||
class="form-control" |
|||
autofocus |
|||
/> |
|||
</div> |
|||
<p>{{ 'AbpUiMultiTenancy::SwitchTenantHint' | abpLocalization }}</p> |
|||
</div> |
|||
</form> |
|||
</ng-template> |
|||
<ng-template #abpFooter> |
|||
<button abpClose type="button" class="btn btn-secondary"> |
|||
{{ 'AbpTenantManagement::Cancel' | abpLocalization }} |
|||
</button> |
|||
<abp-button |
|||
type="abp-button" |
|||
iconClass="fa fa-check" |
|||
(click)="service.save()" |
|||
[disabled]="currentTenant?.name === service.name" |
|||
> |
|||
<span>{{ 'AbpTenantManagement::Save' | abpLocalization }}</span> |
|||
</abp-button> |
|||
</ng-template> |
|||
</abp-modal> |
|||
</ng-container> |
|||
@ -0,0 +1,25 @@ |
|||
import { ComponentFixture, TestBed } from '@angular/core/testing'; |
|||
|
|||
import { TenantBoxComponent } from './tenant-box.component'; |
|||
|
|||
describe('TenantBoxComponent', () => { |
|||
let component: TenantBoxComponent; |
|||
let fixture: ComponentFixture<TenantBoxComponent>; |
|||
|
|||
beforeEach(async () => { |
|||
await TestBed.configureTestingModule({ |
|||
declarations: [ TenantBoxComponent ] |
|||
}) |
|||
.compileComponents(); |
|||
}); |
|||
|
|||
beforeEach(() => { |
|||
fixture = TestBed.createComponent(TenantBoxComponent); |
|||
component = fixture.componentInstance; |
|||
fixture.detectChanges(); |
|||
}); |
|||
|
|||
it('should create', () => { |
|||
expect(component).toBeTruthy(); |
|||
}); |
|||
}); |
|||
@ -0,0 +1,13 @@ |
|||
import { Component, OnInit } from '@angular/core'; |
|||
import { TenantBoxService } from '@abp/ng.account.core'; |
|||
|
|||
@Component({ |
|||
selector: 'abp-tenant-box', |
|||
templateUrl: './tenant-box.component.html', |
|||
providers: [TenantBoxService], |
|||
}) |
|||
export class TenantBoxComponent implements OnInit { |
|||
constructor(public service: TenantBoxService) {} |
|||
|
|||
ngOnInit(): void {} |
|||
} |
|||
@ -0,0 +1,50 @@ |
|||
<nav |
|||
class="navbar navbar-expand-lg navbar-dark bg-dark shadow-sm flex-column flex-md-row mb-4" |
|||
id="main-navbar" |
|||
style="min-height: 4rem" |
|||
> |
|||
<div class="container"> |
|||
<abp-logo *abpReplaceableTemplate="{ componentKey: service.logoComponentKey }"></abp-logo> |
|||
<button |
|||
class="navbar-toggler" |
|||
type="button" |
|||
[attr.aria-expanded]="!service.isCollapsed" |
|||
(click)="service.isCollapsed = !service.isCollapsed" |
|||
> |
|||
<span class="navbar-toggler-icon"></span> |
|||
</button> |
|||
<div class="navbar-collapse" [class.overflow-hidden]="service.smallScreen" id="main-navbar-collapse"> |
|||
<ng-container *ngTemplateOutlet="!service.smallScreen ? navigations : null"></ng-container> |
|||
|
|||
<div *ngIf="service.smallScreen" [@collapseWithMargin]="service.isCollapsed ? 'collapsed' : 'expanded'"> |
|||
<ng-container *ngTemplateOutlet="navigations"></ng-container> |
|||
</div> |
|||
|
|||
<ng-template #navigations> |
|||
<abp-routes |
|||
*abpReplaceableTemplate="{ |
|||
componentKey: service.routesComponentKey, |
|||
inputs: { |
|||
smallScreen: { value: service.smallScreen } |
|||
} |
|||
}" |
|||
class="mx-auto" |
|||
[smallScreen]="service.smallScreen" |
|||
></abp-routes> |
|||
|
|||
<abp-nav-items |
|||
*abpReplaceableTemplate="{ |
|||
componentKey: service.navItemsComponentKey |
|||
}" |
|||
></abp-nav-items> |
|||
</ng-template> |
|||
</div> |
|||
</div> |
|||
</nav> |
|||
|
|||
<!-- [@slideFromBottom]="outlet.isActivated && outlet.activatedRoute?.routeConfig?.path" TODO: throws ExpressionChangedAfterItHasBeenCheck when animation is active. It should be fixed --> |
|||
<div class="container"> |
|||
<abp-page-alert-container></abp-page-alert-container> |
|||
|
|||
<router-outlet #outlet="outlet"></router-outlet> |
|||
</div> |
|||
@ -0,0 +1,21 @@ |
|||
import { eLayoutType, SubscriptionService } from '@abp/ng.core'; |
|||
import { collapseWithMargin, slideFromBottom } from '@abp/ng.theme.shared'; |
|||
import { AfterViewInit, Component } from '@angular/core'; |
|||
import { LayoutService } from '../../services/layout.service'; |
|||
|
|||
@Component({ |
|||
selector: 'abp-layout-application', |
|||
templateUrl: './application-layout.component.html', |
|||
animations: [slideFromBottom, collapseWithMargin], |
|||
providers: [LayoutService, SubscriptionService], |
|||
}) |
|||
export class ApplicationLayoutComponent implements AfterViewInit { |
|||
// required for dynamic component
|
|||
static type = eLayoutType.application; |
|||
|
|||
constructor(public service: LayoutService) {} |
|||
|
|||
ngAfterViewInit() { |
|||
this.service.subscribeWindowSize(); |
|||
} |
|||
} |
|||
@ -0,0 +1,13 @@ |
|||
import { Component } from '@angular/core'; |
|||
import { eLayoutType } from '@abp/ng.core'; |
|||
|
|||
@Component({ |
|||
selector: 'abp-layout-empty', |
|||
template: ` |
|||
<router-outlet></router-outlet> |
|||
<abp-confirmation></abp-confirmation> |
|||
`,
|
|||
}) |
|||
export class EmptyLayoutComponent { |
|||
static type = eLayoutType.empty; |
|||
} |
|||
@ -0,0 +1,12 @@ |
|||
export * from './account-layout/account-layout.component'; |
|||
export * from './account-layout/auth-wrapper/auth-wrapper.component'; |
|||
export * from './account-layout/tenant-box/tenant-box.component'; |
|||
export * from './application-layout/application-layout.component'; |
|||
export * from './empty-layout/empty-layout.component'; |
|||
export * from './logo/logo.component'; |
|||
export * from './nav-items/current-user.component'; |
|||
export * from './nav-items/languages.component'; |
|||
export * from './nav-items/nav-items.component'; |
|||
export * from './routes/routes.component'; |
|||
export * from './validation-error/validation-error.component'; |
|||
export * from './page-alert-container/page-alert-container.component'; |
|||
@ -0,0 +1,28 @@ |
|||
import { ApplicationInfo, EnvironmentService } from '@abp/ng.core'; |
|||
import { Component } from '@angular/core'; |
|||
|
|||
@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(): ApplicationInfo { |
|||
return this.environment.getEnvironment().application; |
|||
} |
|||
|
|||
constructor(private environment: EnvironmentService) {} |
|||
} |
|||
@ -0,0 +1,41 @@ |
|||
<ng-template #loginBtn> |
|||
<a role="button" class="nav-link pointer" (click)="navigateToLogin()">{{ |
|||
'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" |
|||
> |
|||
<small *ngIf="(selectedTenant$ | async)?.name as tenantName" |
|||
><i>{{ tenantName }}</i |
|||
>\</small |
|||
> |
|||
<strong>{{ (currentUser$ | async)?.userName }}</strong> |
|||
</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 pointer" (click)="navigateToManageProfile()" |
|||
><i class="fa fa-cog mr-1"></i>{{ 'AbpAccount::MyAccount' | 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> |
|||
@ -0,0 +1,37 @@ |
|||
import { |
|||
AuthService, |
|||
ConfigStateService, |
|||
CurrentUserDto, |
|||
NAVIGATE_TO_MANAGE_PROFILE, |
|||
SessionStateService, |
|||
} from '@abp/ng.core'; |
|||
import { Component, Inject } from '@angular/core'; |
|||
import { Observable } from 'rxjs'; |
|||
|
|||
@Component({ |
|||
selector: 'abp-current-user', |
|||
templateUrl: './current-user.component.html', |
|||
}) |
|||
export class CurrentUserComponent { |
|||
currentUser$: Observable<CurrentUserDto> = this.configState.getOne$('currentUser'); |
|||
selectedTenant$ = this.sessionState.getTenant$(); |
|||
|
|||
get smallScreen(): boolean { |
|||
return window.innerWidth < 992; |
|||
} |
|||
|
|||
constructor( |
|||
@Inject(NAVIGATE_TO_MANAGE_PROFILE) public navigateToManageProfile, |
|||
private authService: AuthService, |
|||
private configState: ConfigStateService, |
|||
private sessionState: SessionStateService, |
|||
) {} |
|||
|
|||
navigateToLogin() { |
|||
this.authService.navigateToLogin(); |
|||
} |
|||
|
|||
logout() { |
|||
this.authService.logout().subscribe(); |
|||
} |
|||
} |
|||
@ -0,0 +1,84 @@ |
|||
import { ConfigStateService, LanguageInfo, SessionStateService } from '@abp/ng.core'; |
|||
import { Component } from '@angular/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 { |
|||
get smallScreen(): boolean { |
|||
return window.innerWidth < 992; |
|||
} |
|||
|
|||
languages$: Observable<LanguageInfo[]> = this.configState.getDeep$('localization.languages'); |
|||
|
|||
get defaultLanguage$(): Observable<string> { |
|||
return this.languages$.pipe( |
|||
map( |
|||
languages => |
|||
snq( |
|||
() => languages.find(lang => lang.cultureName === this.selectedLangCulture).displayName, |
|||
), |
|||
'', |
|||
), |
|||
); |
|||
} |
|||
|
|||
get dropdownLanguages$(): Observable<LanguageInfo[]> { |
|||
return this.languages$.pipe( |
|||
map( |
|||
languages => |
|||
snq(() => languages.filter(lang => lang.cultureName !== this.selectedLangCulture)), |
|||
[], |
|||
), |
|||
); |
|||
} |
|||
|
|||
get selectedLangCulture(): string { |
|||
return this.sessionState.getLanguage(); |
|||
} |
|||
|
|||
constructor(private sessionState: SessionStateService, private configState: ConfigStateService) {} |
|||
|
|||
onChangeLang(cultureName: string) { |
|||
this.sessionState.setLanguage(cultureName); |
|||
} |
|||
} |
|||
@ -0,0 +1,16 @@ |
|||
<ul class="navbar-nav"> |
|||
<ng-container *ngFor="let item of navItems.items$ | async; trackBy: trackByFn"> |
|||
<ng-container *ngIf="item.visible()"> |
|||
<li class="nav-item d-flex align-items-center" *abpPermission="item.requiredPolicy"> |
|||
<ng-container |
|||
*ngIf="item.component; else htmlTemplate" |
|||
[ngComponentOutlet]="item.component" |
|||
></ng-container> |
|||
|
|||
<ng-template #htmlTemplate> |
|||
<div [innerHTML]="item.html" (click)="item.action ? item.action() : null"></div> |
|||
</ng-template> |
|||
</li> |
|||
</ng-container> |
|||
</ng-container> |
|||
</ul> |
|||
@ -0,0 +1,12 @@ |
|||
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 { |
|||
trackByFn: TrackByFunction<NavItem> = (_, element) => element.id; |
|||
|
|||
constructor(public readonly navItems: NavItemsService) {} |
|||
} |
|||
@ -0,0 +1,22 @@ |
|||
<ng-container *ngFor="let alert of service.alerts$ | async; let i = index"> |
|||
<div |
|||
class="alert alert-{{ alert.type }} fade show" |
|||
[ngClass]="{ 'alert-dismissible fade show': alert.dismissible }" |
|||
role="alert" |
|||
> |
|||
<h4 class="alert-heading" *ngIf="alert.title"> |
|||
{{ alert.title | abpLocalization: alert.titleLocalizationParams }} |
|||
</h4> |
|||
{{ alert.message | abpLocalization: alert.messageLocalizationParams }} |
|||
<button |
|||
*ngIf="alert.dismissible" |
|||
type="button" |
|||
class="close" |
|||
data-dismiss="alert" |
|||
aria-label="Close" |
|||
(click)="service.remove(i)" |
|||
> |
|||
<span aria-hidden="true">×</span> |
|||
</button> |
|||
</div> |
|||
</ng-container> |
|||
@ -0,0 +1,11 @@ |
|||
import { Component, ViewEncapsulation } from '@angular/core'; |
|||
import { PageAlertService } from '@abp/ng.theme.shared'; |
|||
|
|||
@Component({ |
|||
selector: 'abp-page-alert-container', |
|||
templateUrl: './page-alert-container.component.html', |
|||
encapsulation: ViewEncapsulation.None, |
|||
}) |
|||
export class PageAlertContainerComponent { |
|||
constructor(public service: PageAlertService) {} |
|||
} |
|||
@ -0,0 +1,104 @@ |
|||
<ul class="navbar-nav"> |
|||
<ng-container |
|||
*ngFor="let route of routesService.visible$ | async; trackBy: trackByFn" |
|||
[ngTemplateOutlet]="isDropdown(route) ? 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.path]" |
|||
><i *ngIf="route.iconClass" [ngClass]="route.iconClass"></i> |
|||
{{ route.name | abpLocalization }}</a |
|||
> |
|||
</li> |
|||
</ng-template> |
|||
|
|||
<ng-template #dropdownLink let-route> |
|||
<ng-container *ngIf="route.children?.length"> |
|||
<li |
|||
#navbarRootDropdown |
|||
class="nav-item dropdown" |
|||
display="static" |
|||
*abpPermission="route.requiredPolicy" |
|||
(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-container |
|||
*ngTemplateOutlet="forTemplate; context: { $implicit: route }" |
|||
></ng-container> |
|||
</div> |
|||
</li> |
|||
</ng-container> |
|||
</ng-template> |
|||
|
|||
<ng-template #forTemplate let-route> |
|||
<ng-container *ngFor="let child of route.children"> |
|||
<ng-template |
|||
[ngTemplateOutlet]="child.children?.length ? dropdownChild : defaultChild" |
|||
[ngTemplateOutletContext]="{ $implicit: child }" |
|||
></ng-template> |
|||
</ng-container> |
|||
</ng-template> |
|||
|
|||
<ng-template #defaultChild let-child> |
|||
<ng-container *ngIf="child.path"> |
|||
<div class="dropdown-submenu" *abpPermission="child.requiredPolicy"> |
|||
<a class="dropdown-item" [routerLink]="[child.path]" (click)="closeDropdown()"> |
|||
<i *ngIf="child.iconClass" [ngClass]="child.iconClass"></i> |
|||
{{ child.name | abpLocalization }}</a |
|||
> |
|||
</div> |
|||
</ng-container> |
|||
</ng-template> |
|||
|
|||
<ng-template #dropdownChild let-child> |
|||
<div |
|||
class="dropdown-submenu" |
|||
ngbDropdown |
|||
#dropdownSubmenu="ngbDropdown" |
|||
placement="right-top" |
|||
[autoClose]="true" |
|||
*abpPermission="child.requiredPolicy" |
|||
> |
|||
<div ngbDropdownToggle [class.dropdown-toggle]="false"> |
|||
<a |
|||
abpEllipsis="210px" |
|||
[abpEllipsisEnabled]="!smallScreen" |
|||
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-container *ngTemplateOutlet="forTemplate; context: { $implicit: child }"></ng-container> |
|||
</div> |
|||
</div> |
|||
</ng-template> |
|||
</ul> |
|||
@ -0,0 +1,35 @@ |
|||
import { ABP, RoutesService, TreeNode } from '@abp/ng.core'; |
|||
import { |
|||
Component, |
|||
ElementRef, |
|||
Input, |
|||
QueryList, |
|||
Renderer2, |
|||
TrackByFunction, |
|||
ViewChildren, |
|||
} from '@angular/core'; |
|||
|
|||
@Component({ |
|||
selector: 'abp-routes', |
|||
templateUrl: 'routes.component.html', |
|||
}) |
|||
export class RoutesComponent { |
|||
@Input() smallScreen: boolean; |
|||
|
|||
@ViewChildren('childrenContainer') childrenContainers: QueryList<ElementRef<HTMLDivElement>>; |
|||
|
|||
trackByFn: TrackByFunction<TreeNode<ABP.Route>> = (_, item) => item.name; |
|||
|
|||
constructor(public readonly routesService: RoutesService, protected renderer: Renderer2) {} |
|||
|
|||
isDropdown(node: TreeNode<ABP.Route>) { |
|||
return !node?.isLeaf || this.routesService.hasChildren(node.name); |
|||
} |
|||
|
|||
closeDropdown() { |
|||
this.childrenContainers.forEach(({ nativeElement }) => { |
|||
this.renderer.addClass(nativeElement, 'd-none'); |
|||
setTimeout(() => this.renderer.removeClass(nativeElement, 'd-none'), 0); |
|||
}); |
|||
} |
|||
} |
|||
@ -0,0 +1,34 @@ |
|||
import { ChangeDetectionStrategy, Component, ViewEncapsulation } from '@angular/core'; |
|||
import { Validation, ValidationErrorComponent as ErrorComponent } from '@ngx-validate/core'; |
|||
|
|||
@Component({ |
|||
selector: 'abp-validation-error', |
|||
template: ` |
|||
<div class="invalid-feedback" *ngFor="let error of abpErrors; trackBy: trackByFn"> |
|||
{{ error.message | abpLocalization: error.interpoliteParams }} |
|||
</div> |
|||
`,
|
|||
changeDetection: ChangeDetectionStrategy.OnPush, |
|||
encapsulation: ViewEncapsulation.None, |
|||
}) |
|||
export class ValidationErrorComponent extends ErrorComponent { |
|||
get abpErrors(): Validation.Error[] & { interpoliteParams?: string[] } { |
|||
if (!this.errors || !this.errors.length) return []; |
|||
|
|||
return this.errors.map(error => { |
|||
if (!error.message) return error; |
|||
|
|||
const index = error.message.indexOf('['); |
|||
|
|||
if (index > -1) { |
|||
return { |
|||
...error, |
|||
message: error.message.slice(0, index), |
|||
interpoliteParams: error.message.slice(index + 1, error.message.length - 1).split(','), |
|||
}; |
|||
} |
|||
|
|||
return error; |
|||
}); |
|||
} |
|||
} |
|||
@ -0,0 +1,145 @@ |
|||
export default ` |
|||
.content-header-title { |
|||
font-size: 24px; |
|||
} |
|||
.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; |
|||
} |
|||
[dir=rtl] .navbar .dropdown-menu a { |
|||
text-align: right!important; |
|||
} |
|||
.navbar .dropdown-submenu a::after { |
|||
transform: rotate(-90deg); |
|||
position: absolute; |
|||
right: 16px; |
|||
top: 18px; |
|||
} |
|||
[dir=rtl] .navbar .dropdown-submenu a::after { |
|||
transform: rotate(90deg); |
|||
left: 16px; |
|||
right: auto; |
|||
top: 20px; |
|||
} |
|||
.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: 992px) { |
|||
.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; |
|||
} |
|||
.ui-table .ui-table-tbody > tr.empty-row > div.empty-row-content { |
|||
border: 1px solid #c8c8c8; |
|||
} |
|||
.abp-loading { |
|||
background: rgba(0, 0, 0, 0.05); |
|||
} |
|||
.modal-backdrop { |
|||
background-color: rgba(0, 0, 0, 0.6); |
|||
} |
|||
|
|||
.confirmation .confirmation-backdrop { |
|||
background: rgba(0, 0, 0, 0.7) !important; |
|||
} |
|||
.confirmation .confirmation-dialog { |
|||
border: none; |
|||
border-radius: 10px; |
|||
background-color: #fff; |
|||
box-shadow: 0 0 10px -5px rgba(0, 0, 0, 0.5); |
|||
} |
|||
.confirmation .confirmation-dialog .icon-container .icon { |
|||
stroke: #fff; |
|||
color: #fff; |
|||
} |
|||
.confirmation .confirmation-dialog .icon-container.info .icon { |
|||
stroke: #2f96b4; |
|||
color: #2f96b4; |
|||
} |
|||
.confirmation .confirmation-dialog .icon-container.success .icon { |
|||
stroke: #51a351; |
|||
color: #51a351; |
|||
} |
|||
.confirmation .confirmation-dialog .icon-container.warning .icon { |
|||
stroke: #f89406; |
|||
color: #f89406; |
|||
} |
|||
.confirmation .confirmation-dialog .icon-container.error .icon { |
|||
stroke: #bd362f; |
|||
color: #bd362f; |
|||
} |
|||
.confirmation .confirmation-dialog .content .title { |
|||
color: #222; |
|||
} |
|||
.confirmation .confirmation-dialog .content .message { |
|||
color: #777; |
|||
} |
|||
.confirmation .confirmation-dialog .footer { |
|||
background: transparent; |
|||
} |
|||
.confirmation .confirmation-dialog .footer .confirmation-button { |
|||
background-color: #eee; |
|||
color: #777; |
|||
} |
|||
.confirmation .confirmation-dialog .footer .confirmation-button:hover, .confirmation .confirmation-dialog .footer .confirmation-button:focus, .confirmation .confirmation-dialog .footer .confirmation-button:active { |
|||
background-color: #bbb; |
|||
} |
|||
.confirmation .confirmation-dialog .footer .confirmation-button--confirm { |
|||
background-color: #2f96b4; |
|||
color: #fff; |
|||
} |
|||
.confirmation .confirmation-dialog .footer .confirmation-button--confirm:hover { |
|||
background-color: #2e819b; |
|||
} |
|||
.ui-table .pagination-wrapper { |
|||
background-color: #f4f4f4; |
|||
border: 1px solid #c8c8c8; |
|||
} |
|||
.bordered .datatable-body-row { |
|||
border-top: 1px solid #eee; |
|||
margin-top: -1px; |
|||
} |
|||
.breadcrumb { |
|||
background-color: transparent; |
|||
padding: 0.27rem; |
|||
} |
|||
`;
|
|||
@ -0,0 +1,10 @@ |
|||
export const enum eThemeBasicComponents { |
|||
ApplicationLayout = 'Theme.ApplicationLayoutComponent', |
|||
AccountLayout = 'Theme.AccountLayoutComponent', |
|||
EmptyLayout = 'Theme.EmptyLayoutComponent', |
|||
Logo = 'Theme.LogoComponent', |
|||
Routes = 'Theme.RoutesComponent', |
|||
NavItems = 'Theme.NavItemsComponent', |
|||
CurrentUser = 'Theme.CurrentUserComponent', |
|||
Languages = 'Theme.LanguagesComponent', |
|||
} |
|||
@ -0,0 +1 @@ |
|||
export * from './components'; |
|||
@ -0,0 +1 @@ |
|||
export * from './layout'; |
|||
@ -0,0 +1,13 @@ |
|||
import { TemplateRef } from '@angular/core'; |
|||
|
|||
export namespace Layout { |
|||
export interface State { |
|||
navigationElements: NavigationElement[]; |
|||
} |
|||
|
|||
export interface NavigationElement { |
|||
name: string; |
|||
element: TemplateRef<any>; |
|||
order?: number; |
|||
} |
|||
} |
|||
@ -0,0 +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: 100, |
|||
component: CurrentUserComponent, |
|||
}, |
|||
]); |
|||
}; |
|||
} |
|||
@ -0,0 +1,43 @@ |
|||
import { ReplaceableComponentsService, CONTENT_STRATEGY, DomInsertionService } from '@abp/ng.core'; |
|||
import { APP_INITIALIZER } from '@angular/core'; |
|||
import { Store } from '@ngxs/store'; |
|||
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 styles from '../constants/styles'; |
|||
import { eThemeBasicComponents } from '../enums/components'; |
|||
|
|||
export const BASIC_THEME_STYLES_PROVIDERS = [ |
|||
{ |
|||
provide: APP_INITIALIZER, |
|||
useFactory: configureStyles, |
|||
deps: [DomInsertionService, ReplaceableComponentsService], |
|||
multi: true, |
|||
}, |
|||
]; |
|||
|
|||
export function configureStyles( |
|||
domInsertion: DomInsertionService, |
|||
replaceableComponents: ReplaceableComponentsService, |
|||
) { |
|||
return () => { |
|||
domInsertion.insertContent(CONTENT_STRATEGY.AppendStyleToHead(styles)); |
|||
|
|||
initLayouts(replaceableComponents); |
|||
}; |
|||
} |
|||
|
|||
function initLayouts(replaceableComponents: ReplaceableComponentsService) { |
|||
replaceableComponents.add({ |
|||
key: eThemeBasicComponents.ApplicationLayout, |
|||
component: ApplicationLayoutComponent, |
|||
}); |
|||
replaceableComponents.add({ |
|||
key: eThemeBasicComponents.AccountLayout, |
|||
component: AccountLayoutComponent, |
|||
}); |
|||
replaceableComponents.add({ |
|||
key: eThemeBasicComponents.EmptyLayout, |
|||
component: EmptyLayoutComponent, |
|||
}); |
|||
} |
|||
@ -0,0 +1,39 @@ |
|||
import { ChangeDetectorRef, Injectable } from '@angular/core'; |
|||
import { eThemeBasicComponents } from '../enums'; |
|||
import { SubscriptionService } from '@abp/ng.core'; |
|||
import { fromEvent } from 'rxjs'; |
|||
import { debounceTime } from 'rxjs/operators'; |
|||
|
|||
@Injectable() |
|||
export class LayoutService { |
|||
isCollapsed = true; |
|||
|
|||
smallScreen: boolean; // do not set true or false
|
|||
|
|||
logoComponentKey = eThemeBasicComponents.Logo; |
|||
|
|||
routesComponentKey = eThemeBasicComponents.Routes; |
|||
|
|||
navItemsComponentKey = eThemeBasicComponents.NavItems; |
|||
|
|||
constructor(private subscription: SubscriptionService, private cdRef: ChangeDetectorRef) {} |
|||
|
|||
private checkWindowWidth() { |
|||
const isSmallScreen = window.innerWidth < 992; |
|||
if (isSmallScreen && this.smallScreen === false) { |
|||
this.isCollapsed = false; |
|||
setTimeout(() => { |
|||
this.isCollapsed = true; |
|||
}, 100); |
|||
} |
|||
this.smallScreen = isSmallScreen; |
|||
this.cdRef.detectChanges(); |
|||
} |
|||
|
|||
subscribeWindowSize() { |
|||
this.checkWindowWidth(); |
|||
|
|||
const resize$ = fromEvent(window, 'resize').pipe(debounceTime(150)); |
|||
this.subscription.addOne(resize$, () => this.checkWindowWidth()); |
|||
} |
|||
} |
|||
@ -0,0 +1,88 @@ |
|||
import { CoreModule } from '@abp/ng.core'; |
|||
import { ThemeSharedModule } from '@abp/ng.theme.shared'; |
|||
import { ModuleWithProviders, NgModule } from '@angular/core'; |
|||
import { NgbCollapseModule, NgbDropdownModule } from '@ng-bootstrap/ng-bootstrap'; |
|||
import { |
|||
NgxValidateCoreModule, |
|||
VALIDATION_ERROR_TEMPLATE, |
|||
VALIDATION_INVALID_CLASSES, |
|||
VALIDATION_TARGET_SELECTOR, |
|||
} from '@ngx-validate/core'; |
|||
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 { LogoComponent } from './components/logo/logo.component'; |
|||
import { CurrentUserComponent } from './components/nav-items/current-user.component'; |
|||
import { LanguagesComponent } from './components/nav-items/languages.component'; |
|||
import { NavItemsComponent } from './components/nav-items/nav-items.component'; |
|||
import { RoutesComponent } from './components/routes/routes.component'; |
|||
import { ValidationErrorComponent } from './components/validation-error/validation-error.component'; |
|||
import { BASIC_THEME_NAV_ITEM_PROVIDERS } from './providers/nav-item.provider'; |
|||
import { BASIC_THEME_STYLES_PROVIDERS } from './providers/styles.provider'; |
|||
import { PageAlertContainerComponent } from './components/page-alert-container/page-alert-container.component'; |
|||
import { TenantBoxComponent } from './components/account-layout/tenant-box/tenant-box.component'; |
|||
import { AuthWrapperComponent } from './components/account-layout/auth-wrapper/auth-wrapper.component'; |
|||
|
|||
export const LAYOUTS = [ApplicationLayoutComponent, AccountLayoutComponent, EmptyLayoutComponent]; |
|||
|
|||
@NgModule({ |
|||
declarations: [ |
|||
...LAYOUTS, |
|||
ValidationErrorComponent, |
|||
LogoComponent, |
|||
NavItemsComponent, |
|||
RoutesComponent, |
|||
CurrentUserComponent, |
|||
LanguagesComponent, |
|||
PageAlertContainerComponent, |
|||
TenantBoxComponent, |
|||
AuthWrapperComponent, |
|||
], |
|||
exports: [ |
|||
...LAYOUTS, |
|||
ValidationErrorComponent, |
|||
LogoComponent, |
|||
NavItemsComponent, |
|||
RoutesComponent, |
|||
CurrentUserComponent, |
|||
LanguagesComponent, |
|||
PageAlertContainerComponent, |
|||
], |
|||
imports: [ |
|||
CoreModule, |
|||
ThemeSharedModule, |
|||
NgbCollapseModule, |
|||
NgbDropdownModule, |
|||
NgxValidateCoreModule, |
|||
], |
|||
entryComponents: [...LAYOUTS, ValidationErrorComponent, CurrentUserComponent, LanguagesComponent], |
|||
}) |
|||
export class BaseThemeBasicModule {} |
|||
|
|||
@NgModule({ |
|||
exports: [BaseThemeBasicModule], |
|||
imports: [BaseThemeBasicModule], |
|||
}) |
|||
export class ThemeBasicModule { |
|||
static forRoot(): ModuleWithProviders<ThemeBasicModule> { |
|||
return { |
|||
ngModule: ThemeBasicModule, |
|||
providers: [ |
|||
BASIC_THEME_NAV_ITEM_PROVIDERS, |
|||
BASIC_THEME_STYLES_PROVIDERS, |
|||
{ |
|||
provide: VALIDATION_ERROR_TEMPLATE, |
|||
useValue: ValidationErrorComponent, |
|||
}, |
|||
{ |
|||
provide: VALIDATION_TARGET_SELECTOR, |
|||
useValue: '.form-group', |
|||
}, |
|||
{ |
|||
provide: VALIDATION_INVALID_CLASSES, |
|||
useValue: 'is-invalid', |
|||
}, |
|||
], |
|||
}; |
|||
} |
|||
} |
|||
@ -0,0 +1,9 @@ |
|||
/* |
|||
* Public API Surface of theme-basic |
|||
*/ |
|||
|
|||
export * from './lib/components'; |
|||
export * from './lib/enums'; |
|||
export * from './lib/models'; |
|||
export * from './lib/providers'; |
|||
export * from './lib/theme-basic.module'; |
|||
@ -0,0 +1 @@ |
|||
import 'jest-preset-angular/setup-jest'; |
|||
@ -0,0 +1,7 @@ |
|||
{ |
|||
"$schema": "../../../node_modules/ng-packagr/ng-package.schema.json", |
|||
"dest": "../../../dist/libs/theme-basic/testing", |
|||
"lib": { |
|||
"entryFile": "src/public-api.ts" |
|||
} |
|||
} |
|||
@ -0,0 +1,40 @@ |
|||
import { |
|||
BaseThemeBasicModule, |
|||
BASIC_THEME_NAV_ITEM_PROVIDERS, |
|||
BASIC_THEME_STYLES_PROVIDERS, |
|||
ValidationErrorComponent, |
|||
} from '@abp/ng.theme.basic'; |
|||
import { ModuleWithProviders, NgModule } from '@angular/core'; |
|||
import { |
|||
VALIDATION_ERROR_TEMPLATE, |
|||
VALIDATION_INVALID_CLASSES, |
|||
VALIDATION_TARGET_SELECTOR, |
|||
} from '@ngx-validate/core'; |
|||
|
|||
@NgModule({ |
|||
exports: [BaseThemeBasicModule], |
|||
imports: [BaseThemeBasicModule], |
|||
}) |
|||
export class ThemeBasicTestingModule { |
|||
static withConfig(): ModuleWithProviders<ThemeBasicTestingModule> { |
|||
return { |
|||
ngModule: ThemeBasicTestingModule, |
|||
providers: [ |
|||
BASIC_THEME_NAV_ITEM_PROVIDERS, |
|||
BASIC_THEME_STYLES_PROVIDERS, |
|||
{ |
|||
provide: VALIDATION_ERROR_TEMPLATE, |
|||
useValue: ValidationErrorComponent, |
|||
}, |
|||
{ |
|||
provide: VALIDATION_TARGET_SELECTOR, |
|||
useValue: '.form-group', |
|||
}, |
|||
{ |
|||
provide: VALIDATION_INVALID_CLASSES, |
|||
useValue: 'is-invalid', |
|||
}, |
|||
], |
|||
}; |
|||
} |
|||
} |
|||
@ -0,0 +1 @@ |
|||
export * from './lib/theme-basic-testing.module'; |
|||
@ -0,0 +1,16 @@ |
|||
{ |
|||
"extends": "../../tsconfig.base.json", |
|||
"files": [], |
|||
"include": [], |
|||
"references": [ |
|||
{ |
|||
"path": "./tsconfig.lib.json" |
|||
}, |
|||
{ |
|||
"path": "./tsconfig.spec.json" |
|||
} |
|||
], |
|||
"compilerOptions": { |
|||
"allowSyntheticDefaultImports": true, |
|||
} |
|||
} |
|||
@ -0,0 +1,14 @@ |
|||
{ |
|||
"extends": "./tsconfig.json", |
|||
"compilerOptions": { |
|||
"outDir": "../../dist/out-tsc", |
|||
"target": "es2015", |
|||
"declaration": true, |
|||
"declarationMap": true, |
|||
"inlineSources": true, |
|||
"types": [], |
|||
"lib": ["dom", "es2018"] |
|||
}, |
|||
"exclude": ["src/test-setup.ts", "**/*.spec.ts"], |
|||
"include": ["**/*.ts"] |
|||
} |
|||
@ -0,0 +1,11 @@ |
|||
/* To learn more about this file see: https://angular.io/config/tsconfig. */ |
|||
{ |
|||
"extends": "./tsconfig.lib.json", |
|||
"compilerOptions": { |
|||
"declarationMap": false |
|||
}, |
|||
"angularCompilerOptions": { |
|||
"compilationMode": "partial", |
|||
"enableIvy": false |
|||
} |
|||
} |
|||
@ -0,0 +1,10 @@ |
|||
{ |
|||
"extends": "./tsconfig.json", |
|||
"compilerOptions": { |
|||
"outDir": "../../dist/out-tsc", |
|||
"module": "commonjs", |
|||
"types": ["jest", "node"] |
|||
}, |
|||
"files": ["src/test-setup.ts"], |
|||
"include": ["**/*.spec.ts", "**/*.d.ts"] |
|||
} |
|||
@ -1,7 +1,3 @@ |
|||
# theme-shared |
|||
<h1> @abp/ng.theme.shared </h1> |
|||
|
|||
This library was generated with [Nx](https://nx.dev). |
|||
|
|||
## Running unit tests |
|||
|
|||
Run `nx test theme-shared` to execute the unit tests. |
|||
[docs.abp.io](https://docs.abp.io) |
|||
|
|||
Loading…
Reference in new issue