From 38b20f83501c92e7e814cbf203b453a483566e7c Mon Sep 17 00:00:00 2001 From: TheDiaval Date: Thu, 16 Jan 2020 13:14:01 +0300 Subject: [PATCH 1/9] refactor(theme-shared): remove confirmation colors --- .../confirmation/confirmation.component.html | 6 +-- .../confirmation/confirmation.component.scss | 47 ++----------------- 2 files changed, 6 insertions(+), 47 deletions(-) diff --git a/npm/ng-packs/packages/theme-shared/src/lib/components/confirmation/confirmation.component.html b/npm/ng-packs/packages/theme-shared/src/lib/components/confirmation/confirmation.component.html index 07faa3a8a7..f2b1417f14 100644 --- a/npm/ng-packs/packages/theme-shared/src/lib/components/confirmation/confirmation.component.html +++ b/npm/ng-packs/packages/theme-shared/src/lib/components/confirmation/confirmation.component.html @@ -1,4 +1,4 @@ -
+
@@ -15,7 +15,7 @@
@@ -23,6 +24,7 @@ tr.empty-row > div.empty-row-content { border: 1px solid #c8c8c8; } + +.abp-loading { + background: rgba(0, 0, 0, 0.1); +} `; diff --git a/npm/ng-packs/packages/theme-shared/src/lib/components/loading/loading.component.ts b/npm/ng-packs/packages/theme-shared/src/lib/components/loading/loading.component.ts index 0271ced29b..c68757b4a0 100644 --- a/npm/ng-packs/packages/theme-shared/src/lib/components/loading/loading.component.ts +++ b/npm/ng-packs/packages/theme-shared/src/lib/components/loading/loading.component.ts @@ -1,4 +1,4 @@ -import { Component, OnInit } from '@angular/core'; +import { Component, OnInit, ViewEncapsulation } from '@angular/core'; @Component({ selector: 'abp-loading', @@ -7,10 +7,10 @@ import { Component, OnInit } from '@angular/core';
`, + encapsulation: ViewEncapsulation.None, styles: [ ` .abp-loading { - background: rgba(0, 0, 0, 0.2); position: absolute; width: 100%; height: 100%; @@ -23,6 +23,7 @@ import { Component, OnInit } from '@angular/core'; position: absolute; top: 50%; left: 50%; + font-size: 14px; -moz-transform: translateX(-50%) translateY(-50%); -o-transform: translateX(-50%) translateY(-50%); -ms-transform: translateX(-50%) translateY(-50%); diff --git a/npm/ng-packs/packages/theme-shared/src/lib/directives/loading.directive.ts b/npm/ng-packs/packages/theme-shared/src/lib/directives/loading.directive.ts index d38b3713b7..40b76cbfc8 100644 --- a/npm/ng-packs/packages/theme-shared/src/lib/directives/loading.directive.ts +++ b/npm/ng-packs/packages/theme-shared/src/lib/directives/loading.directive.ts @@ -1,22 +1,23 @@ import { - Directive, - ElementRef, - AfterViewInit, - ViewContainerRef, ComponentFactoryResolver, - Input, - Injector, ComponentRef, - ComponentFactory, - HostBinding, + Directive, + ElementRef, EmbeddedViewRef, - Renderer2, + HostBinding, + Injector, + Input, OnInit, + OnDestroy, + Renderer2, + ViewContainerRef, } from '@angular/core'; +import { Subscription, timer } from 'rxjs'; +import { take } from 'rxjs/operators'; import { LoadingComponent } from '../components/loading/loading.component'; @Directive({ selector: '[abpLoading]' }) -export class LoadingDirective implements OnInit { +export class LoadingDirective implements OnInit, OnDestroy { private _loading: boolean; @HostBinding('style.position') @@ -29,29 +30,50 @@ export class LoadingDirective implements OnInit { set loading(newValue: boolean) { setTimeout(() => { - if (!this.componentRef) { - this.componentRef = this.cdRes - .resolveComponentFactory(LoadingComponent) - .create(this.injector); - } + if (!newValue && this.timerSubscription) { + this.timerSubscription.unsubscribe(); + this.timerSubscription = null; + this._loading = newValue; - if (newValue && !this.rootNode) { - this.rootNode = (this.componentRef.hostView as EmbeddedViewRef).rootNodes[0]; - this.targetElement.appendChild(this.rootNode); - } else { - this.renderer.removeChild(this.rootNode.parentElement, this.rootNode); - this.rootNode = null; + if (this.rootNode) { + this.renderer.removeChild(this.rootNode.parentElement, this.rootNode); + this.rootNode = null; + } + return; } - this._loading = newValue; + this.timerSubscription = timer(this.delay) + .pipe(take(1)) + .subscribe(() => { + if (!this.componentRef) { + this.componentRef = this.cdRes + .resolveComponentFactory(LoadingComponent) + .create(this.injector); + } + + if (newValue && !this.rootNode) { + this.rootNode = (this.componentRef.hostView as EmbeddedViewRef).rootNodes[0]; + this.targetElement.appendChild(this.rootNode); + } else { + this.renderer.removeChild(this.rootNode.parentElement, this.rootNode); + this.rootNode = null; + } + + this._loading = newValue; + this.timerSubscription = null; + }); }, 0); } @Input('abpLoadingTargetElement') targetElement: HTMLElement; + @Input('abpLoadingDelay') + delay = 0; + componentRef: ComponentRef; rootNode: HTMLDivElement; + timerSubscription: Subscription; constructor( private elRef: ElementRef, @@ -71,4 +93,10 @@ export class LoadingDirective implements OnInit { } } } + + ngOnDestroy() { + if (this.timerSubscription) { + this.timerSubscription.unsubscribe(); + } + } } diff --git a/npm/ng-packs/packages/theme-shared/src/lib/tests/loading.directive.spec.ts b/npm/ng-packs/packages/theme-shared/src/lib/tests/loading.directive.spec.ts index 1b49b8581c..b359a2000c 100644 --- a/npm/ng-packs/packages/theme-shared/src/lib/tests/loading.directive.spec.ts +++ b/npm/ng-packs/packages/theme-shared/src/lib/tests/loading.directive.spec.ts @@ -20,8 +20,8 @@ describe('LoadingDirective', () => { describe('default', () => { beforeEach(() => { - spectator = createDirective('
Testing Loading Directive
', { - hostProps: { status: true }, + spectator = createDirective('
Testing Loading Directive
', { + hostProps: { loading: true }, }); }); @@ -30,7 +30,7 @@ describe('LoadingDirective', () => { expect(spectator.directive.rootNode).toBeTruthy(); expect(spectator.directive.componentRef).toBeTruthy(); done(); - }, 0); + }, 20); }); }); @@ -40,9 +40,9 @@ describe('LoadingDirective', () => { beforeEach(() => { spectator = createDirective( - '
Testing Loading Directive
', + '
Testing Loading Directive
', { - hostProps: { status: true, target: mockTarget }, + hostProps: { loading: true, target: mockTarget, delay: 0 }, }, ); }); @@ -51,24 +51,35 @@ describe('LoadingDirective', () => { setTimeout(() => { expect(spy).toHaveBeenCalled(); done(); - }, 0); + }, 20); }); it('should remove the loading component to the DOM', done => { const rendererSpy = jest.spyOn(spectator.directive['renderer'], 'removeChild'); - spectator.setHostInput({ status: false }); + setTimeout(() => spectator.setHostInput({ loading: false }), 0); setTimeout(() => { expect(rendererSpy).toHaveBeenCalled(); expect(spectator.directive.rootNode).toBeFalsy(); done(); - }, 0); + }, 20); + }); + + it('should appear with delay', done => { + spectator.setHostInput({ loading: false, delay: 20 }); + spectator.detectChanges(); + setTimeout(() => spectator.setHostInput({ loading: true }), 0); + setTimeout(() => expect(spectator.directive.loading).toBe(false), 15); + setTimeout(() => { + expect(spectator.directive.loading).toBe(true); + done(); + }, 50); }); }); describe('with a component selector', () => { beforeEach(() => { - spectator = createDirective('', { - hostProps: { status: true }, + spectator = createDirective('', { + hostProps: { loading: true }, }); }); @@ -76,7 +87,7 @@ describe('LoadingDirective', () => { setTimeout(() => { expect(spectator.directive.targetElement.id).toBe('dummy'); done(); - }, 0); + }, 20); }); }); }); diff --git a/npm/ng-packs/packages/theme-shared/src/lib/tests/toaster.service.spec.ts b/npm/ng-packs/packages/theme-shared/src/lib/tests/toaster.service.spec.ts index d7fbd7219e..23e99b8204 100644 --- a/npm/ng-packs/packages/theme-shared/src/lib/tests/toaster.service.spec.ts +++ b/npm/ng-packs/packages/theme-shared/src/lib/tests/toaster.service.spec.ts @@ -41,7 +41,7 @@ describe('ToasterService', () => { expect(spectator.query('div.toast')).toBeTruthy(); expect(spectator.query('.toast-icon i')).toHaveClass('fa-times-circle'); expect(spectator.query('div.toast-title')).toHaveText('title'); - expect(spectator.query('div.toast-message')).toHaveText('test'); + expect(spectator.query('p.toast-message')).toHaveText('test'); }); test('should display a warning toast', () => { @@ -71,7 +71,7 @@ describe('ToasterService', () => { 'summary1', 'summary2', ]); - expect(spectator.queryAll('div.toast-message').map(node => node.textContent.trim())).toEqual([ + expect(spectator.queryAll('p.toast-message').map(node => node.textContent.trim())).toEqual([ 'detail1', 'detail2', ]); From 6ff38b645d921b5f668d26dc2586a15a9836eaab Mon Sep 17 00:00:00 2001 From: mehmet-erim Date: Fri, 17 Jan 2020 14:29:53 +0300 Subject: [PATCH 5/9] feat(identity): set default roles as true in new user modal --- .../identity/src/lib/components/users/users.component.html | 3 +-- .../identity/src/lib/components/users/users.component.ts | 4 +++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/npm/ng-packs/packages/identity/src/lib/components/users/users.component.html b/npm/ng-packs/packages/identity/src/lib/components/users/users.component.html index 82995a423f..534bdee970 100644 --- a/npm/ng-packs/packages/identity/src/lib/components/users/users.component.html +++ b/npm/ng-packs/packages/identity/src/lib/components/users/users.component.html @@ -31,6 +31,7 @@ this.fb.group({ [role.name]: [ - !!snq(() => this.selectedUserRoles.find(userRole => userRole.id === role.id)), + this.selectedUserRoles.length + ? !!snq(() => this.selectedUserRoles.find(userRole => userRole.id === role.id)) + : role.isDefault, ], }), ), From 395a7aebfc6458968407b01d5cba3b2fd879f12d Mon Sep 17 00:00:00 2001 From: mehmet-erim Date: Fri, 17 Jan 2020 14:36:28 +0300 Subject: [PATCH 6/9] fix: remove a button that use for debug --- .../identity/src/lib/components/roles/roles.component.html | 1 - 1 file changed, 1 deletion(-) diff --git a/npm/ng-packs/packages/identity/src/lib/components/roles/roles.component.html b/npm/ng-packs/packages/identity/src/lib/components/roles/roles.component.html index 32ade3186c..99a7554c05 100644 --- a/npm/ng-packs/packages/identity/src/lib/components/roles/roles.component.html +++ b/npm/ng-packs/packages/identity/src/lib/components/roles/roles.component.html @@ -15,7 +15,6 @@ {{ 'AbpIdentity::NewRole' | abpLocalization }} -
From e2300072ec5a5d2823d47213faf1a6bf7bd40fb5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Fri, 17 Jan 2020 19:48:08 +0300 Subject: [PATCH 7/9] Added Entities with GUID Keys section. --- docs/en/Entities.md | 60 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) diff --git a/docs/en/Entities.md b/docs/en/Entities.md index 8e0a039dfd..a822e54270 100644 --- a/docs/en/Entities.md +++ b/docs/en/Entities.md @@ -21,6 +21,66 @@ public class Book : Entity `Entity` class just defines an `Id` property with the given primary **key type**, which is `Guid` in the example above. It can be other types like `string`, `int`, `long` or whatever you need. +### Entities with GUID Keys + +If your entity's Id type is `Guid`, there are some good practices to implement: + +* Create a constructor that gets the Id as a parameter and passes to the base class. + * If you leave it default, ABP Framework sets it on save, but it is good to have a valid Id on the entity even before saving it to the database. +* If you create an entity with a constructor that takes parameters, also create a `protected` empty constructor. This is used while your database provider reads your entity from the database (on deserialization). +* Don't use the `Guid.NewGuid()` to set the Id! Use [the `IGuidGenerator` service](Guid-Generation.md) while passing the Id from the code that creates the entity. `IGuidGenerator` optimized to generate sequential GUIDs, which is critical for clustered indexes in the relational databases. + +An example entity: + +````csharp +public class Book : Entity +{ + public string Name { get; set; } + + public float Price { get; set; } + + protected Book() + { + + } + + public Book(Guid id) + : base(id) + { + + } +} +```` + +Example usage in an application service: + +````csharp +public class BookAppService : ApplicationService, IBookAppService +{ + private readonly IRepository _bookRepository; + + public BookAppService(IRepository bookRepository) + { + _bookRepository = bookRepository; + } + + public async Task CreateAsync(CreateBookDto input) + { + await _bookRepository.InsertAsync( + new Book(GuidGenerator.Create()) + { + Name = input.Name, + Price = input.Price + } + ); + } +} +```` + +* `BookAppService` injects the default [repository](Repositories.md) for the book entity and uses its `InsertAsync` method to insert a `Book` to the database. +* `GuidGenerator` is type of `IGuidGenerator` which is a property defined in the `ApplicationService` base class. ABP defines such frequently used base properties as pre-injected for you, so you don't need to manually [inject](Dependency-Injection.md) them. +* If you want to follow the DDD best practices, see the *Aggregate Example* section below. + ### Entities with Composite Keys Some entities may need to have **composite keys**. In that case, you can derive your entity from the non-generic `Entity` class. Example: From 8120ebf9dda27c53030d9ab41c45b06e97791afa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Fri, 17 Jan 2020 19:49:36 +0300 Subject: [PATCH 8/9] Update Entities.md --- docs/en/Entities.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/en/Entities.md b/docs/en/Entities.md index a822e54270..b587eecfe1 100644 --- a/docs/en/Entities.md +++ b/docs/en/Entities.md @@ -52,7 +52,7 @@ public class Book : Entity } ```` -Example usage in an application service: +Example usage in an [application service](Application-Services.md): ````csharp public class BookAppService : ApplicationService, IBookAppService From dda8c713809f7e598f970bceab2e059a0bb60900 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Fri, 17 Jan 2020 19:51:56 +0300 Subject: [PATCH 9/9] Update Entities.md --- docs/en/Entities.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/en/Entities.md b/docs/en/Entities.md index b587eecfe1..b5d196cccb 100644 --- a/docs/en/Entities.md +++ b/docs/en/Entities.md @@ -26,7 +26,7 @@ public class Book : Entity If your entity's Id type is `Guid`, there are some good practices to implement: * Create a constructor that gets the Id as a parameter and passes to the base class. - * If you leave it default, ABP Framework sets it on save, but it is good to have a valid Id on the entity even before saving it to the database. + * If you don't set a GUID Id, ABP Framework sets it on save, but it is good to have a valid Id on the entity even before saving it to the database. * If you create an entity with a constructor that takes parameters, also create a `protected` empty constructor. This is used while your database provider reads your entity from the database (on deserialization). * Don't use the `Guid.NewGuid()` to set the Id! Use [the `IGuidGenerator` service](Guid-Generation.md) while passing the Id from the code that creates the entity. `IGuidGenerator` optimized to generate sequential GUIDs, which is critical for clustered indexes in the relational databases.