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). |
[docs.abp.io](https://docs.abp.io) |
||||
|
|
||||
## Running unit tests |
|
||||
|
|
||||
Run `nx test core` to execute the unit tests. |
|
||||
|
|||||
@ -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). |
[docs.abp.io](https://docs.abp.io) |
||||
|
|
||||
## Running unit tests |
|
||||
|
|
||||
Run `nx test theme-shared` to execute the unit tests. |
|
||||
|
|||||
Loading…
Reference in new issue