From e98faf0571d395c854a2bb7e7a86ef008566288c Mon Sep 17 00:00:00 2001 From: liangshiwei Date: Fri, 5 Jun 2020 18:22:11 +0800 Subject: [PATCH 01/92] Allow set datatables default configuration --- .../datatables/datatables-extensions.js | 73 +++++++++++-------- 1 file changed, 44 insertions(+), 29 deletions(-) diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/wwwroot/libs/abp/aspnetcore-mvc-ui-theme-shared/datatables/datatables-extensions.js b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/wwwroot/libs/abp/aspnetcore-mvc-ui-theme-shared/datatables/datatables-extensions.js index 40aebbc71f..e8489ad62c 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/wwwroot/libs/abp/aspnetcore-mvc-ui-theme-shared/datatables/datatables-extensions.js +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/wwwroot/libs/abp/aspnetcore-mvc-ui-theme-shared/datatables/datatables-extensions.js @@ -8,8 +8,8 @@ }; /************************************************************************ - * RECORD-ACTIONS extension for datatables * - *************************************************************************/ + * RECORD-ACTIONS extension for datatables * + *************************************************************************/ (function () { if (!$.fn.dataTableExt) { return; @@ -226,8 +226,8 @@ })(); /************************************************************************ - * AJAX extension for datatables * - *************************************************************************/ + * AJAX extension for datatables * + *************************************************************************/ (function () { datatables.createAjax = function (serverMethod, inputAction) { return function (requestData, callback, settings) { @@ -275,8 +275,8 @@ })(); /************************************************************************ - * Configuration/Options normalizer for datatables * - *************************************************************************/ + * Configuration/Options normalizer for datatables * + *************************************************************************/ (function () { var customizeRowActionColumn = function (column) { @@ -291,7 +291,7 @@ datatables.normalizeConfiguration = function (configuration) { - configuration.scrollX = true; + configuration.scrollX = datatables.defaultConfigurations.scrollX; for (var i = 0; i < configuration.columnDefs.length; i++) { var column = configuration.columnDefs[i]; @@ -304,34 +304,22 @@ } } - configuration.language = { - info: localize("PagerInfo"), - infoFiltered: localize("PagerInfoFiltered"), - infoEmpty: localize("PagerInfoEmpty"), - search: localize("PagerSearch"), - processing: localize("ProcessingWithThreeDot"), - loadingRecords: localize("LoadingWithThreeDot"), - lengthMenu: localize("PagerShowMenuEntries"), - emptyTable: localize("NoDataAvailableInDatatable"), - paginate: { - first: localize("PagerFirst"), - last: localize("PagerLast"), - previous: localize("PagerPrevious"), - next: localize("PagerNext") - } - }; + configuration.language = datatables.defaultConfigurations.language; - configuration.dom = '<"dataTable_filters"f>rt<"row dataTable_footer"<"col-auto"l><"col-auto"i><"col"p>>'; + if(configuration.dom){ + configuration.dom += datatables.defaultConfigurations.dom; + }else{ + configuration.dom = datatables.defaultConfigurations.dom; + } return configuration; }; - })(); /************************************************************************ - * Default Renderers * - *************************************************************************/ - + * Default Renderers * + *************************************************************************/ + datatables.defaultRenderers = datatables.defaultRenderers || {}; datatables.defaultRenderers['boolean'] = function(value) { @@ -356,4 +344,31 @@ .toLocaleString(luxon.DateTime.DATETIME_SHORT); }; -})(jQuery); \ No newline at end of file + /************************************************************************ + * Default Configurations * + *************************************************************************/ + + datatables.defaultConfigurations = datatables.defaultConfigurations || {}; + + datatables.defaultConfigurations.scrollX = true; + + datatables.defaultConfigurations.language = { + info: localize("PagerInfo"), + infoFiltered: localize("PagerInfoFiltered"), + infoEmpty: localize("PagerInfoEmpty"), + search: localize("PagerSearch"), + processing: localize("ProcessingWithThreeDot"), + loadingRecords: localize("LoadingWithThreeDot"), + lengthMenu: localize("PagerShowMenuEntries"), + emptyTable: localize("NoDataAvailableInDatatable"), + paginate: { + first: localize("PagerFirst"), + last: localize("PagerLast"), + previous: localize("PagerPrevious"), + next: localize("PagerNext") + } + }; + + datatables.defaultConfigurations.dom = '<"dataTable_filters"f>rt<"row dataTable_footer"<"col-auto"l><"col-auto"i><"col"p>>'; + +})(jQuery); From c2fc5f1671f34ffea60c6ef5ddba02c9d1627573 Mon Sep 17 00:00:00 2001 From: liangshiwei Date: Sat, 13 Jun 2020 00:29:05 +0800 Subject: [PATCH 02/92] Update IIdentityClaimTypeRepository & IIdentityRoleRepository --- .../Identity/IIDentityClaimTypeRepository.cs | 23 ++++++++++-- .../Abp/Identity/IIdentityRoleRepository.cs | 13 ++++--- .../EfCoreIdentityClaimTypeRepository.cs | 36 ++++++++++++++----- .../EfCoreIdentityRoleRepository.cs | 11 ++++++ .../MongoIdentityClaimTypeRepository.cs | 33 ++++++++++++++--- .../MongoDB/MongoIdentityRoleRepository.cs | 15 +++++++- .../IdentityClaimTypeRepository_Tests.cs | 6 ++++ .../Identity/IdentityRoleRepository_Tests.cs | 6 ++++ 8 files changed, 122 insertions(+), 21 deletions(-) diff --git a/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IIDentityClaimTypeRepository.cs b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IIDentityClaimTypeRepository.cs index 0e952bded9..0b803760fb 100644 --- a/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IIDentityClaimTypeRepository.cs +++ b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IIDentityClaimTypeRepository.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Threading; using System.Threading.Tasks; using Volo.Abp.Domain.Repositories; @@ -15,8 +16,24 @@ namespace Volo.Abp.Identity /// An Id value to ignore on checking. /// If there is an entity with given it's ignored. /// - Task AnyAsync(string name, Guid? ignoredId = null); + /// Cancel token + Task AnyAsync( + string name, + Guid? ignoredId = null, + CancellationToken cancellationToken = default + ); - Task> GetListAsync(string sorting, int maxResultCount, int skipCount, string filter); + Task> GetListAsync( + string sorting, + int maxResultCount, + int skipCount, + string filter, + CancellationToken cancellationToken = default + ); + + Task GetCountAsync( + string filter = null, + CancellationToken cancellationToken = default + ); } -} +} \ No newline at end of file diff --git a/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IIdentityRoleRepository.cs b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IIdentityRoleRepository.cs index 590003ca7d..e4d9ed55de 100644 --- a/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IIdentityRoleRepository.cs +++ b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IIdentityRoleRepository.cs @@ -21,15 +21,20 @@ namespace Volo.Abp.Identity string filter = null, bool includeDetails = false, CancellationToken cancellationToken = default - ); - Task> GetListAsync( - IEnumerable ids, - CancellationToken cancellationToken = default + ); + Task> GetListAsync( + IEnumerable ids, + CancellationToken cancellationToken = default ); Task> GetDefaultOnesAsync( bool includeDetails = false, CancellationToken cancellationToken = default ); + + Task GetCountAsync( + string filter = null, + CancellationToken cancellationToken = default + ); } } \ No newline at end of file diff --git a/modules/identity/src/Volo.Abp.Identity.EntityFrameworkCore/Volo/Abp/Identity/EntityFrameworkCore/EfCoreIdentityClaimTypeRepository.cs b/modules/identity/src/Volo.Abp.Identity.EntityFrameworkCore/Volo/Abp/Identity/EntityFrameworkCore/EfCoreIdentityClaimTypeRepository.cs index 6d6dcf3d50..0d0ff499c1 100644 --- a/modules/identity/src/Volo.Abp.Identity.EntityFrameworkCore/Volo/Abp/Identity/EntityFrameworkCore/EfCoreIdentityClaimTypeRepository.cs +++ b/modules/identity/src/Volo.Abp.Identity.EntityFrameworkCore/Volo/Abp/Identity/EntityFrameworkCore/EfCoreIdentityClaimTypeRepository.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; using System.Linq.Dynamic.Core; +using System.Threading; using System.Threading.Tasks; using Microsoft.EntityFrameworkCore; using Volo.Abp.Domain.Repositories.EntityFrameworkCore; @@ -11,20 +12,27 @@ namespace Volo.Abp.Identity.EntityFrameworkCore { public class EfCoreIdentityClaimTypeRepository : EfCoreRepository, IIdentityClaimTypeRepository { - public EfCoreIdentityClaimTypeRepository(IDbContextProvider dbContextProvider) + public EfCoreIdentityClaimTypeRepository(IDbContextProvider dbContextProvider) : base(dbContextProvider) { - } - public virtual async Task AnyAsync(string name, Guid? ignoredId = null) + public virtual async Task AnyAsync( + string name, + Guid? ignoredId = null, + CancellationToken cancellationToken = default) { return await DbSet - .WhereIf(ignoredId != null, ct => ct.Id != ignoredId) - .CountAsync(ct => ct.Name == name) > 0; + .WhereIf(ignoredId != null, ct => ct.Id != ignoredId) + .CountAsync(ct => ct.Name == name, GetCancellationToken(cancellationToken)) > 0; } - public virtual async Task> GetListAsync(string sorting, int maxResultCount, int skipCount, string filter) + public virtual async Task> GetListAsync( + string sorting, + int maxResultCount, + int skipCount, + string filter, + CancellationToken cancellationToken = default) { var identityClaimTypes = await DbSet .WhereIf( @@ -34,9 +42,21 @@ namespace Volo.Abp.Identity.EntityFrameworkCore ) .OrderBy(sorting ?? "name desc") .PageBy(skipCount, maxResultCount) - .ToListAsync(); + .ToListAsync(GetCancellationToken(cancellationToken)); return identityClaimTypes; } + + public async Task GetCountAsync( + string filter = null, + CancellationToken cancellationToken = default) + { + return await DbSet + .WhereIf( + !filter.IsNullOrWhiteSpace(), + u => + u.Name.Contains(filter) + ).LongCountAsync(GetCancellationToken(cancellationToken)); + } } -} +} \ No newline at end of file diff --git a/modules/identity/src/Volo.Abp.Identity.EntityFrameworkCore/Volo/Abp/Identity/EntityFrameworkCore/EfCoreIdentityRoleRepository.cs b/modules/identity/src/Volo.Abp.Identity.EntityFrameworkCore/Volo/Abp/Identity/EntityFrameworkCore/EfCoreIdentityRoleRepository.cs index 2814abd891..c0271fa9e9 100644 --- a/modules/identity/src/Volo.Abp.Identity.EntityFrameworkCore/Volo/Abp/Identity/EntityFrameworkCore/EfCoreIdentityRoleRepository.cs +++ b/modules/identity/src/Volo.Abp.Identity.EntityFrameworkCore/Volo/Abp/Identity/EntityFrameworkCore/EfCoreIdentityRoleRepository.cs @@ -60,6 +60,17 @@ namespace Volo.Abp.Identity.EntityFrameworkCore return await DbSet.IncludeDetails(includeDetails).Where(r => r.IsDefault).ToListAsync(GetCancellationToken(cancellationToken)); } + public async Task GetCountAsync( + string filter = null, + CancellationToken cancellationToken = default) + { + return await DbSet + .WhereIf(!filter.IsNullOrWhiteSpace(), + x => x.Name.Contains(filter) || + x.NormalizedName.Contains(filter)) + .LongCountAsync(GetCancellationToken(cancellationToken)); + } + public override IQueryable WithDetails() { return GetQueryable().IncludeDetails(); diff --git a/modules/identity/src/Volo.Abp.Identity.MongoDB/Volo/Abp/Identity/MongoDB/MongoIdentityClaimTypeRepository.cs b/modules/identity/src/Volo.Abp.Identity.MongoDB/Volo/Abp/Identity/MongoDB/MongoIdentityClaimTypeRepository.cs index 8880bcaf1c..a123868799 100644 --- a/modules/identity/src/Volo.Abp.Identity.MongoDB/Volo/Abp/Identity/MongoDB/MongoIdentityClaimTypeRepository.cs +++ b/modules/identity/src/Volo.Abp.Identity.MongoDB/Volo/Abp/Identity/MongoDB/MongoIdentityClaimTypeRepository.cs @@ -5,6 +5,7 @@ using MongoDB.Driver; using MongoDB.Driver.Linq; using System.Linq; using System.Linq.Dynamic.Core; +using System.Threading; using Volo.Abp.Domain.Repositories.MongoDB; using Volo.Abp.MongoDB; @@ -16,23 +17,31 @@ namespace Volo.Abp.Identity.MongoDB { } - public virtual async Task AnyAsync(string name, Guid? ignoredId = null) + public virtual async Task AnyAsync( + string name, + Guid? ignoredId = null, + CancellationToken cancellationToken = default) { if (ignoredId == null) { return await GetMongoQueryable() .Where(ct => ct.Name == name) - .AnyAsync(); + .AnyAsync(GetCancellationToken(cancellationToken)); } else { return await GetMongoQueryable() .Where(ct => ct.Id != ignoredId && ct.Name == name) - .AnyAsync(); + .AnyAsync(GetCancellationToken(cancellationToken)); } } - public virtual async Task> GetListAsync(string sorting, int maxResultCount, int skipCount, string filter) + public virtual async Task> GetListAsync( + string sorting, + int maxResultCount, + int skipCount, + string filter, + CancellationToken cancellationToken = default) { return await GetMongoQueryable() .WhereIf>( @@ -43,7 +52,21 @@ namespace Volo.Abp.Identity.MongoDB .OrderBy(sorting ?? nameof(IdentityClaimType.Name)) .As>() .PageBy>(skipCount, maxResultCount) - .ToListAsync(); + .ToListAsync(GetCancellationToken(cancellationToken)); + } + + public async Task GetCountAsync( + string filter = null, + CancellationToken cancellationToken = default) + { + return await GetMongoQueryable() + .WhereIf>( + !filter.IsNullOrWhiteSpace(), + u => + u.Name.Contains(filter) + ) + .As>() + .LongCountAsync(GetCancellationToken(cancellationToken)); } } } diff --git a/modules/identity/src/Volo.Abp.Identity.MongoDB/Volo/Abp/Identity/MongoDB/MongoIdentityRoleRepository.cs b/modules/identity/src/Volo.Abp.Identity.MongoDB/Volo/Abp/Identity/MongoDB/MongoIdentityRoleRepository.cs index 2651fbfa6c..f4b0a365c1 100644 --- a/modules/identity/src/Volo.Abp.Identity.MongoDB/Volo/Abp/Identity/MongoDB/MongoIdentityRoleRepository.cs +++ b/modules/identity/src/Volo.Abp.Identity.MongoDB/Volo/Abp/Identity/MongoDB/MongoIdentityRoleRepository.cs @@ -54,9 +54,22 @@ namespace Volo.Abp.Identity.MongoDB } public virtual async Task> GetDefaultOnesAsync( - bool includeDetails = false, CancellationToken cancellationToken = default) + bool includeDetails = false, + CancellationToken cancellationToken = default) { return await GetMongoQueryable().Where(r => r.IsDefault).ToListAsync(cancellationToken: GetCancellationToken(cancellationToken)); } + + public async Task GetCountAsync( + string filter = null, + CancellationToken cancellationToken = default) + { + return await GetMongoQueryable() + .WhereIf(!filter.IsNullOrWhiteSpace(), + x => x.Name.Contains(filter) || + x.NormalizedName.Contains(filter)) + .As>() + .LongCountAsync(GetCancellationToken(cancellationToken)); + } } } \ No newline at end of file diff --git a/modules/identity/test/Volo.Abp.Identity.TestBase/Volo/Abp/Identity/IdentityClaimTypeRepository_Tests.cs b/modules/identity/test/Volo.Abp.Identity.TestBase/Volo/Abp/Identity/IdentityClaimTypeRepository_Tests.cs index 3250c0b04a..c4483aeefa 100644 --- a/modules/identity/test/Volo.Abp.Identity.TestBase/Volo/Abp/Identity/IdentityClaimTypeRepository_Tests.cs +++ b/modules/identity/test/Volo.Abp.Identity.TestBase/Volo/Abp/Identity/IdentityClaimTypeRepository_Tests.cs @@ -36,5 +36,11 @@ namespace Volo.Abp.Identity result2.ShouldBe(false); } + + [Fact] + public async Task GetCountAsync_With_Filter() + { + (await ClaimTypeRepository.GetCountAsync("Age")).ShouldBe(1); + } } } diff --git a/modules/identity/test/Volo.Abp.Identity.TestBase/Volo/Abp/Identity/IdentityRoleRepository_Tests.cs b/modules/identity/test/Volo.Abp.Identity.TestBase/Volo/Abp/Identity/IdentityRoleRepository_Tests.cs index 1f65dd821f..d170a5b838 100644 --- a/modules/identity/test/Volo.Abp.Identity.TestBase/Volo/Abp/Identity/IdentityRoleRepository_Tests.cs +++ b/modules/identity/test/Volo.Abp.Identity.TestBase/Volo/Abp/Identity/IdentityRoleRepository_Tests.cs @@ -53,6 +53,12 @@ namespace Volo.Abp.Identity (await RoleRepository.GetCountAsync()).ShouldBeGreaterThan(0); } + [Fact] + public async Task GetCountAsync_With_Filter() + { + (await RoleRepository.GetCountAsync("admin")).ShouldBe(1); + } + [Fact] public async Task Should_Eager_Load_Role_Collections() { From e5634fcfa592b796c3b28c9157567173848df3e4 Mon Sep 17 00:00:00 2001 From: Arman Ozak Date: Mon, 15 Jun 2020 12:38:06 +0300 Subject: [PATCH 03/92] feat: add LazyModuleFactory --- .../core/src/lib/tests/factory-utils.spec.ts | 38 +++++++++++++++++++ .../core/src/lib/utils/factory-utils.ts | 31 +++++++++++++++ .../packages/core/src/lib/utils/index.ts | 1 + 3 files changed, 70 insertions(+) create mode 100644 npm/ng-packs/packages/core/src/lib/tests/factory-utils.spec.ts create mode 100644 npm/ng-packs/packages/core/src/lib/utils/factory-utils.ts diff --git a/npm/ng-packs/packages/core/src/lib/tests/factory-utils.spec.ts b/npm/ng-packs/packages/core/src/lib/tests/factory-utils.spec.ts new file mode 100644 index 0000000000..06e8a8d044 --- /dev/null +++ b/npm/ng-packs/packages/core/src/lib/tests/factory-utils.spec.ts @@ -0,0 +1,38 @@ +import { Injector, ModuleWithProviders, NgModule } from '@angular/core'; +import { TestBed } from '@angular/core/testing'; +import { LazyModuleFactory } from '../utils/factory-utils'; + +@NgModule() +class Module { + static forChild(): ModuleWithProviders { + return { + ngModule: Module, + providers: [{ provide: 'foo', useValue: 'bar' }], + }; + } +} + +describe('LazyModuleFactory', () => { + const factory = new LazyModuleFactory(Module.forChild()); + + describe('#moduleType', () => { + it('should return the ngModule property of given ModuleWithProviders', () => { + expect(factory.moduleType).toBe(Module); + }); + }); + + describe('#create', () => { + it('should return an instance of NgModuleRef_', () => { + TestBed.configureTestingModule({}); + + const injector = TestBed.inject(Injector); + const moduleRef = factory.create(injector); + + expect('componentFactoryResolver' in moduleRef).toBe(true); + expect('destroy' in moduleRef).toBe(true); + expect('injector' in moduleRef).toBe(true); + expect('instance' in moduleRef).toBe(true); + expect('onDestroy' in moduleRef).toBe(true); + }); + }); +}); diff --git a/npm/ng-packs/packages/core/src/lib/utils/factory-utils.ts b/npm/ng-packs/packages/core/src/lib/utils/factory-utils.ts new file mode 100644 index 0000000000..bc6273fa82 --- /dev/null +++ b/npm/ng-packs/packages/core/src/lib/utils/factory-utils.ts @@ -0,0 +1,31 @@ +import { + Compiler, + Injector, + ModuleWithProviders, + NgModuleFactory, + NgModuleRef, + StaticProvider, + Type, +} from '@angular/core'; + +export class LazyModuleFactory extends NgModuleFactory { + get moduleType(): Type { + return this.moduleWithProviders.ngModule; + } + + constructor(private moduleWithProviders: ModuleWithProviders) { + super(); + } + + create(parentInjector: Injector | null): NgModuleRef { + const injector = Injector.create({ + parent: parentInjector, + providers: this.moduleWithProviders.providers as StaticProvider[], + }); + + const compiler = injector.get(Compiler); + const factory = compiler.compileModuleSync(this.moduleType); + + return factory.create(injector); + } +} diff --git a/npm/ng-packs/packages/core/src/lib/utils/index.ts b/npm/ng-packs/packages/core/src/lib/utils/index.ts index 4dde8134ef..b75fef8efe 100644 --- a/npm/ng-packs/packages/core/src/lib/utils/index.ts +++ b/npm/ng-packs/packages/core/src/lib/utils/index.ts @@ -1,4 +1,5 @@ export * from './common-utils'; +export * from './factory-utils'; export * from './form-utils'; export * from './generator-utils'; export * from './initial-utils'; From 117b47bba633c205f616516e8366ad37cfc0b7ac Mon Sep 17 00:00:00 2001 From: Arman Ozak Date: Tue, 16 Jun 2020 10:35:51 +0300 Subject: [PATCH 04/92] feat: add pushValueTo array util to core --- .../core/src/lib/tests/array-utils.spec.ts | 17 +++++++++++++++++ .../packages/core/src/lib/utils/array-utils.ts | 6 ++++++ .../packages/core/src/lib/utils/index.ts | 1 + 3 files changed, 24 insertions(+) create mode 100644 npm/ng-packs/packages/core/src/lib/tests/array-utils.spec.ts create mode 100644 npm/ng-packs/packages/core/src/lib/utils/array-utils.ts diff --git a/npm/ng-packs/packages/core/src/lib/tests/array-utils.spec.ts b/npm/ng-packs/packages/core/src/lib/tests/array-utils.spec.ts new file mode 100644 index 0000000000..3ccc91461c --- /dev/null +++ b/npm/ng-packs/packages/core/src/lib/tests/array-utils.spec.ts @@ -0,0 +1,17 @@ +import { pushValueTo } from '../utils/array-utils'; + +describe('Array Utils', () => { + describe('#pushValueTo', () => { + test.each` + source | target | expected + ${[]} | ${[0, 1, 2, 3]} | ${[0, 1, 2, 3]} + ${[3]} | ${[0, 1, 2]} | ${[0, 1, 2, 3]} + ${[2, 3]} | ${[0, 1]} | ${[0, 1, 2, 3]} + ${[1, 2, 3]} | ${[0]} | ${[0, 1, 2, 3]} + ${[0, 1, 2, 3]} | ${[]} | ${[0, 1, 2, 3]} + `('should push $source to $target when called in forEach', ({ source, target, expected }) => { + source.forEach(pushValueTo(target)); + expect(target).toEqual(expected); + }); + }); +}); diff --git a/npm/ng-packs/packages/core/src/lib/utils/array-utils.ts b/npm/ng-packs/packages/core/src/lib/utils/array-utils.ts new file mode 100644 index 0000000000..d2d74105b3 --- /dev/null +++ b/npm/ng-packs/packages/core/src/lib/utils/array-utils.ts @@ -0,0 +1,6 @@ +export function pushValueTo(array: T[]) { + return (element: T) => { + array.push(element); + return array; + }; +} diff --git a/npm/ng-packs/packages/core/src/lib/utils/index.ts b/npm/ng-packs/packages/core/src/lib/utils/index.ts index b75fef8efe..ce89b4dddb 100644 --- a/npm/ng-packs/packages/core/src/lib/utils/index.ts +++ b/npm/ng-packs/packages/core/src/lib/utils/index.ts @@ -1,3 +1,4 @@ +export * from './array-utils'; export * from './common-utils'; export * from './factory-utils'; export * from './form-utils'; From 12995cd1f4df9183b533a3885f292611d9c582ee Mon Sep 17 00:00:00 2001 From: mehmet-erim Date: Tue, 16 Jun 2020 11:24:05 +0300 Subject: [PATCH 05/92] chore: remove pagination.component --- .../theme-shared/src/lib/components/index.ts | 1 - .../pagination/pagination.component.html | 38 ------------- .../pagination/pagination.component.ts | 55 ------------------- .../lib/tests/table-sort.directive.spec.ts | 3 +- .../src/lib/tests/table.component.spec.ts | 4 +- .../src/lib/theme-shared.module.ts | 3 - 6 files changed, 3 insertions(+), 101 deletions(-) delete mode 100644 npm/ng-packs/packages/theme-shared/src/lib/components/pagination/pagination.component.html delete mode 100644 npm/ng-packs/packages/theme-shared/src/lib/components/pagination/pagination.component.ts diff --git a/npm/ng-packs/packages/theme-shared/src/lib/components/index.ts b/npm/ng-packs/packages/theme-shared/src/lib/components/index.ts index 7461bd3841..3163d963a7 100644 --- a/npm/ng-packs/packages/theme-shared/src/lib/components/index.ts +++ b/npm/ng-packs/packages/theme-shared/src/lib/components/index.ts @@ -5,7 +5,6 @@ export * from './confirmation/confirmation.component'; export * from './loading/loading.component'; export * from './loader-bar/loader-bar.component'; export * from './modal/modal.component'; -export * from './pagination/pagination.component'; export * from './sort-order-icon/sort-order-icon.component'; export * from './table-empty-message/table-empty-message.component'; export * from './table/table.component'; diff --git a/npm/ng-packs/packages/theme-shared/src/lib/components/pagination/pagination.component.html b/npm/ng-packs/packages/theme-shared/src/lib/components/pagination/pagination.component.html deleted file mode 100644 index b42f45a148..0000000000 --- a/npm/ng-packs/packages/theme-shared/src/lib/components/pagination/pagination.component.html +++ /dev/null @@ -1,38 +0,0 @@ - diff --git a/npm/ng-packs/packages/theme-shared/src/lib/components/pagination/pagination.component.ts b/npm/ng-packs/packages/theme-shared/src/lib/components/pagination/pagination.component.ts deleted file mode 100644 index d6249d4f39..0000000000 --- a/npm/ng-packs/packages/theme-shared/src/lib/components/pagination/pagination.component.ts +++ /dev/null @@ -1,55 +0,0 @@ -import { Component, Input, OnInit, Output, EventEmitter, TrackByFunction } from '@angular/core'; - -@Component({ - selector: 'abp-pagination', - templateUrl: 'pagination.component.html', -}) -/** - * @deprecated - */ -export class PaginationComponent implements OnInit { - private _value = 1; - @Input() - get value(): number { - return this._value; - } - set value(newValue: number) { - if (this._value === newValue) return; - - this._value = newValue; - this.valueChange.emit(newValue); - } - - @Output() - readonly valueChange = new EventEmitter(); - - @Input() - totalPages = 0; - - get pageArray(): number[] { - const count = this.totalPages < 5 ? this.totalPages : 5; - - if (this.value === 1 || this.value === 2) { - return Array.from(new Array(count)).map((_, index) => index + 1); - } else if (this.value === this.totalPages || this.value === this.totalPages - 1) { - return Array.from(new Array(count)).map((_, index) => this.totalPages - count + 1 + index); - } else { - return [this.value - 2, this.value - 1, this.value, this.value + 1, this.value + 2]; - } - } - - trackByFn: TrackByFunction = (_, page) => page; - - ngOnInit() { - if (!this.value || this.value < 1 || this.value > this.totalPages) { - this.value = 1; - } - } - - changePage(page: number) { - if (page < 1) return; - else if (page > this.totalPages) return; - - this.value = page; - } -} diff --git a/npm/ng-packs/packages/theme-shared/src/lib/tests/table-sort.directive.spec.ts b/npm/ng-packs/packages/theme-shared/src/lib/tests/table-sort.directive.spec.ts index 5298af4dbd..ad43a11b87 100644 --- a/npm/ng-packs/packages/theme-shared/src/lib/tests/table-sort.directive.spec.ts +++ b/npm/ng-packs/packages/theme-shared/src/lib/tests/table-sort.directive.spec.ts @@ -2,7 +2,6 @@ import { SpectatorDirective, createDirectiveFactory } from '@ngneat/spectator/je import { TableSortDirective } from '../directives/table-sort.directive'; import { TableComponent } from '../components/table/table.component'; import { DummyLocalizationPipe } from './table.component.spec'; -import { PaginationComponent } from '../components'; import { NgbPaginationModule } from '@ng-bootstrap/ng-bootstrap'; describe('TableSortDirective', () => { @@ -10,7 +9,7 @@ describe('TableSortDirective', () => { let directive: TableSortDirective; const createDirective = createDirectiveFactory({ directive: TableSortDirective, - declarations: [TableComponent, DummyLocalizationPipe, PaginationComponent], + declarations: [TableComponent, DummyLocalizationPipe], imports: [NgbPaginationModule], }); diff --git a/npm/ng-packs/packages/theme-shared/src/lib/tests/table.component.spec.ts b/npm/ng-packs/packages/theme-shared/src/lib/tests/table.component.spec.ts index d2bf2829c7..d456cebd34 100644 --- a/npm/ng-packs/packages/theme-shared/src/lib/tests/table.component.spec.ts +++ b/npm/ng-packs/packages/theme-shared/src/lib/tests/table.component.spec.ts @@ -1,7 +1,7 @@ import { Pipe, PipeTransform } from '@angular/core'; import { NgbPaginationModule } from '@ng-bootstrap/ng-bootstrap'; import { createHostFactory, SpectatorHost } from '@ngneat/spectator/jest'; -import { PaginationComponent, TableComponent } from '../components'; +import { TableComponent } from '../components'; @Pipe({ name: 'abpLocalization', @@ -16,7 +16,7 @@ describe('TableComponent', () => { let spectator: SpectatorHost; const createHost = createHostFactory({ component: TableComponent, - declarations: [PaginationComponent, DummyLocalizationPipe], + declarations: [DummyLocalizationPipe], imports: [NgbPaginationModule], }); diff --git a/npm/ng-packs/packages/theme-shared/src/lib/theme-shared.module.ts b/npm/ng-packs/packages/theme-shared/src/lib/theme-shared.module.ts index 6afd044725..ab9afc5a56 100644 --- a/npm/ng-packs/packages/theme-shared/src/lib/theme-shared.module.ts +++ b/npm/ng-packs/packages/theme-shared/src/lib/theme-shared.module.ts @@ -14,7 +14,6 @@ import { LoaderBarComponent } from './components/loader-bar/loader-bar.component import { LoadingComponent } from './components/loading/loading.component'; import { ModalContainerComponent } from './components/modal/modal-container.component'; import { ModalComponent } from './components/modal/modal.component'; -import { PaginationComponent } from './components/pagination/pagination.component'; import { SortOrderIconComponent } from './components/sort-order-icon/sort-order-icon.component'; import { TableEmptyMessageComponent } from './components/table-empty-message/table-empty-message.component'; import { TableComponent } from './components/table/table.component'; @@ -59,7 +58,6 @@ export function ngxDatatableMessageFactory(store: Store) { LoadingComponent, ModalComponent, ModalContainerComponent, - PaginationComponent, TableComponent, TableEmptyMessageComponent, ToastComponent, @@ -80,7 +78,6 @@ export function ngxDatatableMessageFactory(store: Store) { LoaderBarComponent, LoadingComponent, ModalComponent, - PaginationComponent, TableComponent, TableEmptyMessageComponent, ToastComponent, From d83f008113d8ad3419b969f51d1e9c067a1c981b Mon Sep 17 00:00:00 2001 From: mehmet-erim Date: Tue, 16 Jun 2020 11:24:39 +0300 Subject: [PATCH 06/92] chore: remove requirements option from CoreModule's Root type --- npm/ng-packs/packages/core/src/lib/models/common.ts | 5 ----- 1 file changed, 5 deletions(-) diff --git a/npm/ng-packs/packages/core/src/lib/models/common.ts b/npm/ng-packs/packages/core/src/lib/models/common.ts index 773233857f..08f7681b5e 100644 --- a/npm/ng-packs/packages/core/src/lib/models/common.ts +++ b/npm/ng-packs/packages/core/src/lib/models/common.ts @@ -7,11 +7,6 @@ import { Config } from './config'; export namespace ABP { export interface Root { environment: Partial; - /** - * - * @deprecated To be deleted in v3.0 - */ - requirements?: Config.Requirements; skipGetAppConfiguration?: boolean; sendNullsAsQueryParam?: boolean; } From a7382b5cc99a6edcce181e6ca6a012b7fe1e0752 Mon Sep 17 00:00:00 2001 From: mehmet-erim Date: Tue, 16 Jun 2020 11:25:04 +0300 Subject: [PATCH 07/92] refactor: remove old load method of LazyLoadService --- .../src/lib/services/lazy-load.service.ts | 115 ++---------------- .../src/lib/tests/lazy-load.service.spec.ts | 94 +------------- 2 files changed, 14 insertions(+), 195 deletions(-) diff --git a/npm/ng-packs/packages/core/src/lib/services/lazy-load.service.ts b/npm/ng-packs/packages/core/src/lib/services/lazy-load.service.ts index 4ba89949fd..5d44bb0879 100644 --- a/npm/ng-packs/packages/core/src/lib/services/lazy-load.service.ts +++ b/npm/ng-packs/packages/core/src/lib/services/lazy-load.service.ts @@ -1,8 +1,7 @@ import { Injectable } from '@angular/core'; -import { concat, Observable, of, ReplaySubject, throwError } from 'rxjs'; +import { concat, Observable, of, throwError } from 'rxjs'; import { delay, retryWhen, shareReplay, take, tap } from 'rxjs/operators'; import { LoadingStrategy } from '../strategies'; -import { uuid } from '../utils'; @Injectable({ providedIn: 'root', @@ -10,108 +9,20 @@ import { uuid } from '../utils'; export class LazyLoadService { readonly loaded = new Map(); - loadedLibraries: { [url: string]: ReplaySubject } = {}; + load(strategy: LoadingStrategy, retryTimes?: number, retryDelay?: number): Observable { + if (this.loaded.has(strategy.path)) return of(new CustomEvent('load')); - load(strategy: LoadingStrategy, retryTimes?: number, retryDelay?: number): Observable; - /** - * - * @deprecated Use other overload that requires a strategy as first param - */ - load( - urlOrUrls: string | string[], - type: 'script' | 'style', - content?: string, - targetQuery?: string, - position?: InsertPosition, - ): Observable; - load( - strategyOrUrl: LoadingStrategy | string | string[], - retryTimesOrType?: number | 'script' | 'style', - retryDelayOrContent?: number | string, - targetQuery: string = 'body', - position: InsertPosition = 'beforeend', - ): Observable { - if (strategyOrUrl instanceof LoadingStrategy) { - const strategy = strategyOrUrl; - const retryTimes = typeof retryTimesOrType === 'number' ? retryTimesOrType : 2; - const retryDelay = typeof retryDelayOrContent === 'number' ? retryDelayOrContent : 1000; - - if (this.loaded.has(strategy.path)) return of(new CustomEvent('load')); - - return strategy.createStream().pipe( - retryWhen(error$ => - concat( - error$.pipe(delay(retryDelay), take(retryTimes)), - throwError(new CustomEvent('error')), - ), + return strategy.createStream().pipe( + retryWhen(error$ => + concat( + error$.pipe(delay(retryDelay), take(retryTimes)), + throwError(new CustomEvent('error')), ), - tap(() => this.loaded.set(strategy.path, strategy.element)), - delay(100), - shareReplay({ bufferSize: 1, refCount: true }), - ); - } - - let urlOrUrls = strategyOrUrl; - const content = (retryDelayOrContent as string) || ''; - const type = retryTimesOrType as 'script' | 'style'; - - if (!urlOrUrls && !content) { - return throwError('Should pass url or content'); - } else if (!urlOrUrls && content) { - urlOrUrls = [null]; - } - - if (!Array.isArray(urlOrUrls)) { - urlOrUrls = [urlOrUrls]; - } - - return new Observable(subscriber => { - (urlOrUrls as string[]).forEach((url, index) => { - const key = url ? url.slice(url.lastIndexOf('/') + 1) : uuid(); - - if (this.loadedLibraries[key]) { - subscriber.next(); - subscriber.complete(); - return; - } - - this.loadedLibraries[key] = new ReplaySubject(); - - let library; - if (type === 'script') { - library = document.createElement('script'); - library.type = 'text/javascript'; - if (url) { - (library as HTMLScriptElement).src = url; - } - - (library as HTMLScriptElement).text = content; - } else if (url) { - library = document.createElement('link'); - library.type = 'text/css'; - (library as HTMLLinkElement).rel = 'stylesheet'; - - if (url) { - (library as HTMLLinkElement).href = url; - } - } else { - library = document.createElement('style'); - (library as HTMLStyleElement).textContent = content; - } - - library.onload = () => { - this.loadedLibraries[key].next(); - this.loadedLibraries[key].complete(); - - if (index === urlOrUrls.length - 1) { - subscriber.next(); - subscriber.complete(); - } - }; - - document.querySelector(targetQuery).insertAdjacentElement(position, library); - }); - }); + ), + tap(() => this.loaded.set(strategy.path, strategy.element)), + delay(100), + shareReplay({ bufferSize: 1, refCount: true }), + ); } remove(path: string): boolean { diff --git a/npm/ng-packs/packages/core/src/lib/tests/lazy-load.service.spec.ts b/npm/ng-packs/packages/core/src/lib/tests/lazy-load.service.spec.ts index 4868fb78f3..dc4d937f17 100644 --- a/npm/ng-packs/packages/core/src/lib/tests/lazy-load.service.spec.ts +++ b/npm/ng-packs/packages/core/src/lib/tests/lazy-load.service.spec.ts @@ -1,6 +1,5 @@ -import { createServiceFactory, SpectatorService } from '@ngneat/spectator/jest'; import { of, throwError } from 'rxjs'; -import { catchError, switchMap } from 'rxjs/operators'; +import { switchMap } from 'rxjs/operators'; import { LazyLoadService } from '../services/lazy-load.service'; import { ScriptLoadingStrategy } from '../strategies'; @@ -84,94 +83,3 @@ describe('LazyLoadService', () => { }); }); }); - -describe('LazyLoadService (Deprecated)', () => { - let spectator: SpectatorService; - let service: LazyLoadService; - const scriptElement = document.createElement('script'); - const linkElement = document.createElement('link'); - const styleElement = document.createElement('style'); - const cloneDocument = { ...document }; - - const createService = createServiceFactory({ service: LazyLoadService }); - - beforeEach(() => { - spectator = createService(); - service = spectator.service; - }); - - afterEach(() => (document = { ...cloneDocument })); - - test('should load script with content just one time', done => { - const spy = jest.spyOn(document, 'createElement'); - spy.mockReturnValue(scriptElement); - - service.load('https://abp.io', 'script', 'test').subscribe(res => { - expect( - document.querySelector('script[src="https://abp.io"][type="text/javascript"]').textContent, - ).toMatch('test'); - }); - - scriptElement.onload(null); - - service.load('https://abp.io', 'script', 'test').subscribe(res => { - expect( - document.querySelectorAll('script[src="https://abp.io"][type="text/javascript"]'), - ).toHaveLength(1); - done(); - }); - }); - - test('should load style element', done => { - const spy = jest.spyOn(document, 'createElement'); - spy.mockReturnValue(styleElement); - - const content = '* { color: black; }'; - service.load(null, 'style', content).subscribe(res => { - expect(document.querySelector('style').textContent).toMatch(content); - done(); - }); - - styleElement.onload(null); - }); - - describe('style with url', () => { - beforeEach(() => { - const spy = jest.spyOn(document, 'createElement'); - spy.mockReturnValue(linkElement); - }); - - test('should load an link element', done => { - service.load('https://abp.io', 'style').subscribe(res => { - expect( - document.querySelector('link[type="text/css"][rel="stylesheet"][href="https://abp.io"]'), - ).toBeTruthy(); - done(); - }); - - linkElement.onload(null); - }); - - test('should load link elements', done => { - service.load(['https://abp.io', 'https://volosoft.com'], 'style').subscribe(res => { - expect(document.querySelector('link[href="https://volosoft.com"]')).toBeTruthy(); - done(); - }); - - linkElement.onload(null); - }); - }); - - test('should throw error when required parameters are null', done => { - service - .load(null, 'style') - .pipe( - catchError(err => { - expect(err).toBeTruthy(); - done(); - return of(null); - }), - ) - .subscribe(); - }); -}); From ed41a884bc38c5eb0a8f3531f0c899d7b6082ae3 Mon Sep 17 00:00:00 2001 From: Arman Ozak Date: Tue, 16 Jun 2020 11:25:09 +0300 Subject: [PATCH 08/92] feat: add tree utils to core --- .../core/src/lib/tests/tree-utils.spec.ts | 68 +++++++++++++++++++ .../packages/core/src/lib/utils/index.ts | 1 + .../packages/core/src/lib/utils/tree-utils.ts | 51 ++++++++++++++ 3 files changed, 120 insertions(+) create mode 100644 npm/ng-packs/packages/core/src/lib/tests/tree-utils.spec.ts create mode 100644 npm/ng-packs/packages/core/src/lib/utils/tree-utils.ts diff --git a/npm/ng-packs/packages/core/src/lib/tests/tree-utils.spec.ts b/npm/ng-packs/packages/core/src/lib/tests/tree-utils.spec.ts new file mode 100644 index 0000000000..454c1c6891 --- /dev/null +++ b/npm/ng-packs/packages/core/src/lib/tests/tree-utils.spec.ts @@ -0,0 +1,68 @@ +import { createTreeFromList, TreeNode } from '../utils/tree-utils'; + +const LIST_1 = [ + { id: 1, pid: null }, + { id: 2, pid: 1 }, +]; +const LIST_2 = [ + { id: 1, pid: null }, + { id: 2, pid: 1 }, + { id: 3, pid: 1 }, +]; +const LIST_3 = [ + { id: 1, pid: null }, + { id: 2, pid: 1 }, + { id: 3, pid: 2 }, +]; +const TREE_1 = [ + { id: 1, pid: null, isLeaf: false, children: [{ id: 2, pid: 1, isLeaf: true, children: [] }] }, +]; +const TREE_2 = [ + { + id: 1, + pid: null, + isLeaf: false, + children: [ + { id: 2, pid: 1, isLeaf: true, children: [] }, + { id: 3, pid: 1, isLeaf: true, children: [] }, + ], + }, +]; +const TREE_3 = [ + { + id: 1, + pid: null, + isLeaf: false, + children: [ + { id: 2, pid: 1, isLeaf: false, children: [{ id: 3, pid: 2, isLeaf: true, children: [] }] }, + ], + }, +]; +describe('Tree Utils', () => { + describe('createTreeFromList', () => { + test.each` + list | expected + ${LIST_1} | ${TREE_1} + ${LIST_2} | ${TREE_2} + ${LIST_3} | ${TREE_3} + `('should return $expected when given $list', ({ list, expected }: TestCreateTreeFromList) => { + expect( + createTreeFromList( + list, + x => x.id, + x => x.pid, + ), + ).toEqual(expected); + }); + }); +}); + +interface TestCreateTreeFromList { + list: ModelA[]; + expected: TreeNode[]; +} + +interface ModelA { + id: 1; + pid: null; +} diff --git a/npm/ng-packs/packages/core/src/lib/utils/index.ts b/npm/ng-packs/packages/core/src/lib/utils/index.ts index ce89b4dddb..6f6136b195 100644 --- a/npm/ng-packs/packages/core/src/lib/utils/index.ts +++ b/npm/ng-packs/packages/core/src/lib/utils/index.ts @@ -9,3 +9,4 @@ export * from './localization-utils'; export * from './number-utils'; export * from './route-utils'; export * from './rxjs-utils'; +export * from './tree-utils'; diff --git a/npm/ng-packs/packages/core/src/lib/utils/tree-utils.ts b/npm/ng-packs/packages/core/src/lib/utils/tree-utils.ts new file mode 100644 index 0000000000..86844a4375 --- /dev/null +++ b/npm/ng-packs/packages/core/src/lib/utils/tree-utils.ts @@ -0,0 +1,51 @@ +export class TreeNode { + children: TreeNode[] = []; + isLeaf = true; + + constructor(props: T) { + Object.assign(this, props); + } +} + +export function createTreeFromList>( + list: T[], + keySelector: (item: T) => NodeKey, + parentKeySelector: (item: T) => NodeKey, + valueMapper = (item: T) => new TreeNode(item) as R, +) { + const map = createMapFromList(list, keySelector, valueMapper); + const tree: ReturnType[] = []; + + list.forEach(row => { + const id = keySelector(row); + const parentId = parentKeySelector(row); + const node = map.get(id); + + if (parentId) { + const parent = map.get(parentId); + parent.children.push(node); + parent.isLeaf = false; + } else { + tree.push(node); + } + }); + + return tree; +} + +export function createMapFromList>( + list: T[], + keySelector: (item: T) => NodeKey, + valueMapper = (item: T) => new TreeNode(item) as R, +) { + const map = new Map, ReturnType>(); + list.forEach(row => map.set(keySelector(row), valueMapper(row))); + return map; +} + +type NodeKey = number | string | Symbol; + +interface BranchOrLeaf { + children: BranchOrLeaf[]; + isLeaf: boolean; +} From e4d934c1b6d7468ddbdcfe254ba78538ca9bf6fd Mon Sep 17 00:00:00 2001 From: mehmet-erim Date: Tue, 16 Jun 2020 11:25:32 +0300 Subject: [PATCH 09/92] refactor: import OAuthModule to RootcoreModule --- npm/ng-packs/packages/core/src/lib/core.module.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/npm/ng-packs/packages/core/src/lib/core.module.ts b/npm/ng-packs/packages/core/src/lib/core.module.ts index c0223d28a6..72aeacb29d 100644 --- a/npm/ng-packs/packages/core/src/lib/core.module.ts +++ b/npm/ng-packs/packages/core/src/lib/core.module.ts @@ -117,6 +117,7 @@ export class BaseCoreModule {} NgxsModule.forFeature([ReplaceableComponentsState, ProfileState, SessionState, ConfigState]), NgxsRouterPluginModule.forRoot(), NgxsStoragePluginModule.forRoot({ key: ['SessionState'] }), + OAuthModule.forRoot(), ], }) export class RootCoreModule {} @@ -195,7 +196,6 @@ export class CoreModule { deps: [LocalizationService], useFactory: noop, }, - ...OAuthModule.forRoot().providers, { provide: OAuthStorage, useFactory: storageFactory }, ], }; From bb5a01ee620ab804c696997bd0e96068459c4511 Mon Sep 17 00:00:00 2001 From: mehmet-erim Date: Tue, 16 Jun 2020 11:26:25 +0300 Subject: [PATCH 10/92] chore: remove Toaster.Status enum --- .../packages/theme-shared/src/lib/models/toaster.ts | 9 --------- 1 file changed, 9 deletions(-) diff --git a/npm/ng-packs/packages/theme-shared/src/lib/models/toaster.ts b/npm/ng-packs/packages/theme-shared/src/lib/models/toaster.ts index 16d06e1b60..e7f456fee4 100644 --- a/npm/ng-packs/packages/theme-shared/src/lib/models/toaster.ts +++ b/npm/ng-packs/packages/theme-shared/src/lib/models/toaster.ts @@ -20,13 +20,4 @@ export namespace Toaster { } export type Severity = 'neutral' | 'success' | 'info' | 'warning' | 'error'; - - /** - * @deprecated Status will be removed from toaster model in v3.0 - */ - export enum Status { - confirm = 'confirm', - reject = 'reject', - dismiss = 'dismiss', - } } From 576f2d76d6ee73a5ade11b992b219744a99bb6c7 Mon Sep 17 00:00:00 2001 From: mehmet-erim Date: Tue, 16 Jun 2020 11:26:47 +0300 Subject: [PATCH 11/92] chore: remove closable option from Confirmation.Options --- .../packages/theme-shared/src/lib/models/confirmation.ts | 6 ------ 1 file changed, 6 deletions(-) diff --git a/npm/ng-packs/packages/theme-shared/src/lib/models/confirmation.ts b/npm/ng-packs/packages/theme-shared/src/lib/models/confirmation.ts index 18dceb320d..086942a1c1 100644 --- a/npm/ng-packs/packages/theme-shared/src/lib/models/confirmation.ts +++ b/npm/ng-packs/packages/theme-shared/src/lib/models/confirmation.ts @@ -10,12 +10,6 @@ export namespace Confirmation { hideYesBtn?: boolean; cancelText?: Config.LocalizationParam; yesText?: Config.LocalizationParam; - - /** - * - * @deprecated To be deleted in v2.9 - */ - closable?: boolean; } export interface DialogData { From 080d209a62bd88b2f28fcdbbc1985744e8ab5463 Mon Sep 17 00:00:00 2001 From: mehmet-erim Date: Tue, 16 Jun 2020 11:28:09 +0300 Subject: [PATCH 12/92] feat: set the scrollbarH and virtualization inputs of ngx-datatable initial values --- .../src/lib/directives/ngx-datatable-default.directive.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/npm/ng-packs/packages/theme-shared/src/lib/directives/ngx-datatable-default.directive.ts b/npm/ng-packs/packages/theme-shared/src/lib/directives/ngx-datatable-default.directive.ts index 3672bde3b3..ec6710474f 100644 --- a/npm/ng-packs/packages/theme-shared/src/lib/directives/ngx-datatable-default.directive.ts +++ b/npm/ng-packs/packages/theme-shared/src/lib/directives/ngx-datatable-default.directive.ts @@ -19,5 +19,7 @@ export class NgxDatatableDefaultDirective { this.table.footerHeight = 50; this.table.headerHeight = 50; this.table.rowHeight = 'auto'; + this.table.scrollbarH = true; + this.table.virtualization = false; } } From 66111b9afa8546103d4cbf0b597c77c889d0a7cd Mon Sep 17 00:00:00 2001 From: Arman Ozak Date: Tue, 16 Jun 2020 14:21:28 +0300 Subject: [PATCH 13/92] feat: improve tree util types --- .../packages/core/src/lib/utils/tree-utils.ts | 44 ++++++++++++------- 1 file changed, 27 insertions(+), 17 deletions(-) diff --git a/npm/ng-packs/packages/core/src/lib/utils/tree-utils.ts b/npm/ng-packs/packages/core/src/lib/utils/tree-utils.ts index 86844a4375..607a30ec52 100644 --- a/npm/ng-packs/packages/core/src/lib/utils/tree-utils.ts +++ b/npm/ng-packs/packages/core/src/lib/utils/tree-utils.ts @@ -1,20 +1,24 @@ -export class TreeNode { +export class TreeNodeFactory { children: TreeNode[] = []; isLeaf = true; constructor(props: T) { Object.assign(this, props); } + + static create(props: T) { + return new TreeNodeFactory(props) as TreeNode; + } } -export function createTreeFromList>( +export function createTreeFromList( list: T[], - keySelector: (item: T) => NodeKey, - parentKeySelector: (item: T) => NodeKey, - valueMapper = (item: T) => new TreeNode(item) as R, + keySelector: (item: T) => number | string | Symbol, + parentKeySelector: typeof keySelector, + valueMapper: (item: T) => R, ) { const map = createMapFromList(list, keySelector, valueMapper); - const tree: ReturnType[] = []; + const tree: NodeValue[] = []; list.forEach(row => { const id = keySelector(row); @@ -23,8 +27,8 @@ export function createTreeFromList>( if (parentId) { const parent = map.get(parentId); - parent.children.push(node); - parent.isLeaf = false; + (parent as any).children.push(node); + (parent as any).isLeaf = false; } else { tree.push(node); } @@ -33,19 +37,25 @@ export function createTreeFromList>( return tree; } -export function createMapFromList>( +export function createMapFromList( list: T[], - keySelector: (item: T) => NodeKey, - valueMapper = (item: T) => new TreeNode(item) as R, + keySelector: (item: T) => number | string | Symbol, + valueMapper: (item: T) => R, ) { - const map = new Map, ReturnType>(); + type Key = ReturnType; + type Value = NodeValue; + const map = new Map(); list.forEach(row => map.set(keySelector(row), valueMapper(row))); return map; } -type NodeKey = number | string | Symbol; - -interface BranchOrLeaf { - children: BranchOrLeaf[]; +export type TreeNode = { + [K in keyof T]: T[K]; +} & { + children: TreeNode[]; isLeaf: boolean; -} +}; + +type NodeValue any> = F extends undefined + ? TreeNode + : ReturnType; From c188c6773ee89000c67f8741112f4b730ae1e86d Mon Sep 17 00:00:00 2001 From: Yunus Emre Kalkan Date: Tue, 16 Jun 2020 14:38:47 +0300 Subject: [PATCH 14/92] Update SignalR-Integration.md resolved https://github.com/abpframework/abp/issues/4184 --- docs/en/SignalR-Integration.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/en/SignalR-Integration.md b/docs/en/SignalR-Integration.md index f318967888..5b176a84a5 100644 --- a/docs/en/SignalR-Integration.md +++ b/docs/en/SignalR-Integration.md @@ -234,4 +234,5 @@ Refer to the Microsoft's documentation to [host and scale](https://docs.microsof ## See Also -* [Microsoft SignalR documentation](https://docs.microsoft.com/en-us/aspnet/core/signalr/introduction) \ No newline at end of file +* [Microsoft SignalR documentation](https://docs.microsoft.com/en-us/aspnet/core/signalr/introduction) +* [Real-Time Messaging In A Distributed Architecture Using ABP, SingalR & RabbitMQ](https://volosoft.com/blog/RealTime-Messaging-Distributed-Architecture-Abp-SingalR-RabbitMQ) From 12f9d45c0826648e82e8ba800a3a555a79f75abf Mon Sep 17 00:00:00 2001 From: mehmet-erim Date: Tue, 16 Jun 2020 15:50:37 +0300 Subject: [PATCH 15/92] feat: add getUserAssingableRoles method to identity.service #4291 --- .../identity/src/lib/components/users/users.component.ts | 2 +- .../identity/src/lib/services/identity.service.ts | 9 +++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/npm/ng-packs/packages/identity/src/lib/components/users/users.component.ts b/npm/ng-packs/packages/identity/src/lib/components/users/users.component.ts index aeeb250126..73da8ad38d 100644 --- a/npm/ng-packs/packages/identity/src/lib/components/users/users.component.ts +++ b/npm/ng-packs/packages/identity/src/lib/components/users/users.component.ts @@ -82,7 +82,7 @@ export class UsersComponent implements OnInit { } buildForm() { - this.identityService.getAllRoles().subscribe(({ items }) => { + this.identityService.getUserAssingableRoles().subscribe(({ items }) => { this.roles = items; this.form = this.fb.group({ userName: [this.selected.userName || '', [Validators.required, Validators.maxLength(256)]], diff --git a/npm/ng-packs/packages/identity/src/lib/services/identity.service.ts b/npm/ng-packs/packages/identity/src/lib/services/identity.service.ts index 51e88557fe..7413079279 100644 --- a/npm/ng-packs/packages/identity/src/lib/services/identity.service.ts +++ b/npm/ng-packs/packages/identity/src/lib/services/identity.service.ts @@ -103,6 +103,15 @@ export class IdentityService { return this.rest.request(request, { apiName: this.apiName }); } + getUserAssingableRoles(): Observable { + const request: Rest.Request = { + method: 'GET', + url: '/api/identity/users/assignable-roles', + }; + + return this.rest.request(request, { apiName: this.apiName }); + } + deleteUser(id: string): Observable { const request: Rest.Request = { method: 'DELETE', From 54c8283a96e15d79ef5242ac4bb4a3ea5342fa50 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ak=C4=B1n=20Sabri=20=C3=87am?= Date: Tue, 16 Jun 2020 21:23:20 +0300 Subject: [PATCH 16/92] added localization keys for abp io admin invoice page --- .../AbpIoLocalization/Admin/Localization/Resources/en.json | 2 ++ 1 file changed, 2 insertions(+) diff --git a/abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/en.json b/abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/en.json index 4d81ba9058..9e5afc3b33 100644 --- a/abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/en.json +++ b/abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/en.json @@ -76,6 +76,7 @@ "Organizations": "Organizations", "LongName": "Long name", "LicenseType": "License type", + "MissingLicenseTypeField": "The license type field is required!", "LicenseStartTime": "License start time", "LicenseEndTime": "License end time", "AllowedDeveloperCount": "Allowed developer count", @@ -121,6 +122,7 @@ "TaxNumber": "Tax Number", "InvoiceNumber": "Invoice Number", "InvoiceDate": "Invoice Date", + "InvoiceNote": "Invoice Note", "Quantity": "Quantity", "AddProduct": "Add Product", "AddProductWarning": "You need to add product!", From 1fbb20d655ac42f1b8fd60a95c483891e4086609 Mon Sep 17 00:00:00 2001 From: Yunus Emre Kalkan Date: Wed, 17 Jun 2020 09:58:30 +0300 Subject: [PATCH 17/92] Fix: Feature name Localizations are wrong in feature management modal resolves https://github.com/abpframework/abp/issues/4370 --- .../Pages/FeatureManagement/FeatureManagementModal.cshtml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/feature-management/src/Volo.Abp.FeatureManagement.Web/Pages/FeatureManagement/FeatureManagementModal.cshtml b/modules/feature-management/src/Volo.Abp.FeatureManagement.Web/Pages/FeatureManagement/FeatureManagementModal.cshtml index 221b46bea6..ec8b4b1706 100644 --- a/modules/feature-management/src/Volo.Abp.FeatureManagement.Web/Pages/FeatureManagement/FeatureManagementModal.cshtml +++ b/modules/feature-management/src/Volo.Abp.FeatureManagement.Web/Pages/FeatureManagement/FeatureManagementModal.cshtml @@ -22,7 +22,7 @@ var feature = Model.FeatureListDto.Features[i];
- @feature.Name + @feature.DisplayName @if (feature.ValueType is FreeTextStringValueType) From f4cb8f41461cd37449aae662d9ff065a62e57c60 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Wed, 17 Jun 2020 13:21:53 +0300 Subject: [PATCH 18/92] Added IQueryable & Async Operations section to the repository document --- docs/en/Repositories.md | 130 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 129 insertions(+), 1 deletion(-) diff --git a/docs/en/Repositories.md b/docs/en/Repositories.md index 6a60c58278..96f160a5d1 100644 --- a/docs/en/Repositories.md +++ b/docs/en/Repositories.md @@ -38,6 +38,8 @@ public class PersonAppService : ApplicationService } ```` +> See the "*IQueryable & Async Operations*" section below to understand how you can use **async extension methods**, like `ToListAsync()` (which is strongly suggested) instead of `ToList()`. + In this example; * `PersonAppService` simply injects `IRepository` in it's constructor. @@ -116,5 +118,131 @@ public class PersonRepository : EfCoreRepository, IPe } ```` -You can directly access the data access provider (`DbContext` in this case) to perform operations. See [entity framework integration document](Entity-Framework-Core.md) for more about custom repositories based on EF Core. +You can directly access the data access provider (`DbContext` in this case) to perform operations. + +> See [EF Core](Entity-Framework-Core.md) or [MongoDb](MongoDB.md) document for more info about the custom repositories. + +## IQueryable & Async Operations + +`IRepository` inherits from `IQueryable`, that means you can **directly use LINQ extension methods** on it, as shown in the example of the "*Generic Repositories*" section above. + +**Example: Using the `Where(...)` &and the`ToList()` extension methods** + +````csharp +var people = _personRepository + .Where(p => p.Name.Contains(nameFilter)) + .ToList(); +```` + +`.ToList`, `Count()`... are standard extension methods defined in the `System.Linq` namespace ([see all](https://docs.microsoft.com/en-us/dotnet/api/system.linq.queryable)). + +You normally want to use `.ToListAsync()`, `.CountAsync()`... instead, to be able to write a **truly async code**. + +However, you see that can't use these async extension methods in your application or domain layer when you create a new project using the standard [application startup template](Startup-Templates/Application.md), because; + +* These async methods **are not standard LINQ methods** and they are defined in the [Microsoft.EntityFrameworkCore](https://www.nuget.org/packages/Microsoft.EntityFrameworkCore) NuGet package. +* The standard template **doesn't have a reference** to the EF Core package from the domain and application layers, to be independent from the database provider. + +Based on your requirements and development model, you have the following options to be able to use the async methods. + +> Using async methods is strongly suggested! Don't use sync LINQ methods while executing database queries to be able to develop a scalable application. + +### Option-1: Reference to the EF Core + +The easiest solution is to directly add the EF Core package from the project you want to use these async methods. + +> Add the [Volo.Abp.EntityFrameworkCore](https://www.nuget.org/packages/Volo.Abp.EntityFrameworkCore) NuGet package to your project, which indirectly reference to the EF Core package. This ensures that you use the correct version of the EF Core compatible to the rest of your application. + +When you add the NuGet package to your project, you can take full power of the EF Core extension methods. + +**Example: Directly using the `ToListAsync()` after adding the EF Core package** + +````csharp +var people = _personRepository + .Where(p => p.Name.Contains(nameFilter)) + .ToListAsync(); +```` + +This method is suggested; + +* If you are developing an application and you **don't plan to change** EF Core in the future, or you can **tolerate** it if you need to change later. We believe that's reasonable if you are developing a final application. + +#### MongoDB Case + +If you are using [MongoDB](MongoDB.md), you need to add the [Volo.Abp.MongoDB](https://www.nuget.org/packages/Volo.Abp.MongoDB) NuGet package to your project. Even in this case, you can't directly use async LINQ extensions (like `ToListAsync`) because MongoDB doesn't provide async extension methods for `IQueryable`, but provides for `IMongoQueryable`. You need to cast the query to `IMongoQueryable` first to be able to use the async extension methods. + +**Example: Cast `IQueryable` to `IMongoQueryable` and use `ToListAsync()`** + +````csharp +var people = ((IMongoQueryable)_personRepository + .Where(p => p.Name.Contains(nameFilter))) + .ToListAsync(); +```` + +### Option-2: Custom Repository Methods + +You can always create custom repository methods and use the database provider specific APIs, like async extension methods here. See [EF Core](Entity-Framework-Core.md) or [MongoDb](MongoDB.md) document for more info about the custom repositories. + +This method is suggested; + +* If you want to **completely isolate** your domain & application layers completely from the database provider. +* If you develop a **reusable [application module](Modules/Index.md)** and don't want to depend on a specific database provider. + +### Option-3: IAsyncQueryableExecuter + +> Notice that this feature is available in the ABP Framework 3.0 and later. While it is also usable with older versions, it was providing a very limited set of methods. + +`IAsyncQueryableExecuter` is a service that is used to execute an `IQueryable` object asynchronously **without depending on the actual database provider**. + +**Example: Inject & use the `IAsyncQueryableExecuter.ToListAsync()` method** + +````csharp +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Volo.Abp.Application.Dtos; +using Volo.Abp.Application.Services; +using Volo.Abp.Domain.Repositories; +using Volo.Abp.Linq; + +namespace AbpDemo +{ + public class ProductAppService : ApplicationService, IProductAppService + { + private readonly IRepository _productRepository; + private readonly IAsyncQueryableExecuter _asyncExecuter; + + public ProductAppService( + IRepository productRepository, + IAsyncQueryableExecuter asyncExecuter) + { + _productRepository = productRepository; + _asyncExecuter = asyncExecuter; + } + + public async Task> GetListAsync(string name) + { + //Create the query + var query = _productRepository + .Where(p => p.Name.Contains(name)) + .OrderBy(p => p.Name); + + //Run the query asynchronously + List products = await _asyncExecuter.ToListAsync(query); + + //... + } + } +} +```` + +> `ApplicationService` and `DomainService` base classes already have `AsyncExecuter` properties pre-injected and usable without needing an explicit constructor injection. + +ABP Framework executes the query asynchronously using the actual database provider's API. While that is not a usual way to execute a query, it is the best way to use the async API without depending on the database provider. + +This method is suggested; + +* If you are building a **reusable library** that doesn't have a database provider integration package, but needs to execute an `IQueryable` object in some case. +For example, ABP Framework uses the `IAsyncQueryableExecuter` in the `CrudAppService` base class (see the [application services](Application-Services.md) document). \ No newline at end of file From 6e501dfabc2e8c121674f49a44914e25fa0858cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Wed, 17 Jun 2020 13:45:29 +0300 Subject: [PATCH 19/92] Update Repositories.md --- docs/en/Repositories.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/en/Repositories.md b/docs/en/Repositories.md index 96f160a5d1..86a3e231c5 100644 --- a/docs/en/Repositories.md +++ b/docs/en/Repositories.md @@ -126,7 +126,7 @@ You can directly access the data access provider (`DbContext` in this case) to p `IRepository` inherits from `IQueryable`, that means you can **directly use LINQ extension methods** on it, as shown in the example of the "*Generic Repositories*" section above. -**Example: Using the `Where(...)` &and the`ToList()` extension methods** +**Example: Using the `Where(...)` &and the `ToList()` extension methods** ````csharp var people = _personRepository @@ -138,7 +138,7 @@ var people = _personRepository You normally want to use `.ToListAsync()`, `.CountAsync()`... instead, to be able to write a **truly async code**. -However, you see that can't use these async extension methods in your application or domain layer when you create a new project using the standard [application startup template](Startup-Templates/Application.md), because; +However, you see that you can't use these async extension methods in your application or domain layer when you create a new project using the standard [application startup template](Startup-Templates/Application.md), because; * These async methods **are not standard LINQ methods** and they are defined in the [Microsoft.EntityFrameworkCore](https://www.nuget.org/packages/Microsoft.EntityFrameworkCore) NuGet package. * The standard template **doesn't have a reference** to the EF Core package from the domain and application layers, to be independent from the database provider. @@ -185,8 +185,8 @@ You can always create custom repository methods and use the database provider sp This method is suggested; -* If you want to **completely isolate** your domain & application layers completely from the database provider. -* If you develop a **reusable [application module](Modules/Index.md)** and don't want to depend on a specific database provider. +* If you want to **completely isolate** your domain & application layers from the database provider. +* If you develop a **reusable [application module](Modules/Index.md)** and don't want to force to a specific database provider, which should be done as a [best practice](Best-Practices/Index.md). ### Option-3: IAsyncQueryableExecuter From cdd42c9978af59d779a67e0f31f754cebc453b54 Mon Sep 17 00:00:00 2001 From: Alper Ebicoglu Date: Wed, 17 Jun 2020 14:01:36 +0300 Subject: [PATCH 20/92] recreated initial migrations due to OrganizationUnits change --- ...nit.Designer.cs => 20200617102723_Initial.Designer.cs} | 8 +++++--- .../{20200525053411_init.cs => 20200617102723_Initial.cs} | 2 +- .../Migrations/VoloDocsDbContextModelSnapshot.cs | 4 +++- .../VoloDocs.EntityFrameworkCore.csproj | 5 +++++ modules/docs/app/VoloDocs.Web/VoloDocs.Web.csproj | 4 ---- 5 files changed, 14 insertions(+), 9 deletions(-) rename modules/docs/app/VoloDocs.EntityFrameworkCore/Migrations/{20200525053411_init.Designer.cs => 20200617102723_Initial.Designer.cs} (99%) rename modules/docs/app/VoloDocs.EntityFrameworkCore/Migrations/{20200525053411_init.cs => 20200617102723_Initial.cs} (99%) diff --git a/modules/docs/app/VoloDocs.EntityFrameworkCore/Migrations/20200525053411_init.Designer.cs b/modules/docs/app/VoloDocs.EntityFrameworkCore/Migrations/20200617102723_Initial.Designer.cs similarity index 99% rename from modules/docs/app/VoloDocs.EntityFrameworkCore/Migrations/20200525053411_init.Designer.cs rename to modules/docs/app/VoloDocs.EntityFrameworkCore/Migrations/20200617102723_Initial.Designer.cs index 2de428f388..5fbced9e4a 100644 --- a/modules/docs/app/VoloDocs.EntityFrameworkCore/Migrations/20200525053411_init.Designer.cs +++ b/modules/docs/app/VoloDocs.EntityFrameworkCore/Migrations/20200617102723_Initial.Designer.cs @@ -5,19 +5,21 @@ using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Migrations; using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Volo.Abp.EntityFrameworkCore; using VoloDocs.EntityFrameworkCore; namespace VoloDocs.EntityFrameworkCore.Migrations { [DbContext(typeof(VoloDocsDbContext))] - [Migration("20200525053411_init")] - partial class init + [Migration("20200617102723_Initial")] + partial class Initial { protected override void BuildTargetModel(ModelBuilder modelBuilder) { #pragma warning disable 612, 618 modelBuilder - .HasAnnotation("ProductVersion", "3.1.2") + .HasAnnotation("_Abp_DatabaseProvider", EfCoreDatabaseProvider.SqlServer) + .HasAnnotation("ProductVersion", "3.1.5") .HasAnnotation("Relational:MaxIdentifierLength", 128) .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); diff --git a/modules/docs/app/VoloDocs.EntityFrameworkCore/Migrations/20200525053411_init.cs b/modules/docs/app/VoloDocs.EntityFrameworkCore/Migrations/20200617102723_Initial.cs similarity index 99% rename from modules/docs/app/VoloDocs.EntityFrameworkCore/Migrations/20200525053411_init.cs rename to modules/docs/app/VoloDocs.EntityFrameworkCore/Migrations/20200617102723_Initial.cs index 00854e6745..179387743c 100644 --- a/modules/docs/app/VoloDocs.EntityFrameworkCore/Migrations/20200525053411_init.cs +++ b/modules/docs/app/VoloDocs.EntityFrameworkCore/Migrations/20200617102723_Initial.cs @@ -3,7 +3,7 @@ using Microsoft.EntityFrameworkCore.Migrations; namespace VoloDocs.EntityFrameworkCore.Migrations { - public partial class init : Migration + public partial class Initial : Migration { protected override void Up(MigrationBuilder migrationBuilder) { diff --git a/modules/docs/app/VoloDocs.EntityFrameworkCore/Migrations/VoloDocsDbContextModelSnapshot.cs b/modules/docs/app/VoloDocs.EntityFrameworkCore/Migrations/VoloDocsDbContextModelSnapshot.cs index 216a9a06f4..43feaed625 100644 --- a/modules/docs/app/VoloDocs.EntityFrameworkCore/Migrations/VoloDocsDbContextModelSnapshot.cs +++ b/modules/docs/app/VoloDocs.EntityFrameworkCore/Migrations/VoloDocsDbContextModelSnapshot.cs @@ -4,6 +4,7 @@ using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Volo.Abp.EntityFrameworkCore; using VoloDocs.EntityFrameworkCore; namespace VoloDocs.EntityFrameworkCore.Migrations @@ -15,7 +16,8 @@ namespace VoloDocs.EntityFrameworkCore.Migrations { #pragma warning disable 612, 618 modelBuilder - .HasAnnotation("ProductVersion", "3.1.2") + .HasAnnotation("_Abp_DatabaseProvider", EfCoreDatabaseProvider.SqlServer) + .HasAnnotation("ProductVersion", "3.1.5") .HasAnnotation("Relational:MaxIdentifierLength", 128) .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); diff --git a/modules/docs/app/VoloDocs.EntityFrameworkCore/VoloDocs.EntityFrameworkCore.csproj b/modules/docs/app/VoloDocs.EntityFrameworkCore/VoloDocs.EntityFrameworkCore.csproj index b2b54a234f..2f8204e44e 100644 --- a/modules/docs/app/VoloDocs.EntityFrameworkCore/VoloDocs.EntityFrameworkCore.csproj +++ b/modules/docs/app/VoloDocs.EntityFrameworkCore/VoloDocs.EntityFrameworkCore.csproj @@ -15,4 +15,9 @@ + + + + + diff --git a/modules/docs/app/VoloDocs.Web/VoloDocs.Web.csproj b/modules/docs/app/VoloDocs.Web/VoloDocs.Web.csproj index 3fb341be49..9da16c65cf 100644 --- a/modules/docs/app/VoloDocs.Web/VoloDocs.Web.csproj +++ b/modules/docs/app/VoloDocs.Web/VoloDocs.Web.csproj @@ -20,10 +20,6 @@ - - all - runtime; build; native; contentfiles; analyzers - From 11333312d2834b655098f47094c8a0f7e7a62ad4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?ebubekir=20din=C3=A7?= Date: Wed, 17 Jun 2020 14:18:04 +0300 Subject: [PATCH 21/92] Resource Localization for base and account --- .../Account/Localization/Resources/tr.json | 8 +++++++ .../Base/Localization/Resources/tr.json | 21 +++++++++++++++++-- 2 files changed, 27 insertions(+), 2 deletions(-) diff --git a/abp_io/AbpIoLocalization/AbpIoLocalization/Account/Localization/Resources/tr.json b/abp_io/AbpIoLocalization/AbpIoLocalization/Account/Localization/Resources/tr.json index 5af6a13c50..9c36cd611b 100644 --- a/abp_io/AbpIoLocalization/AbpIoLocalization/Account/Localization/Resources/tr.json +++ b/abp_io/AbpIoLocalization/AbpIoLocalization/Account/Localization/Resources/tr.json @@ -1,5 +1,13 @@ { "culture": "tr", "texts": { + "Account": "ABP Hesabı - Giriş Yap & Kayıt Ol | ABP.IO", + "Welcome": "Hoş Geldiniz", + "UseOneOfTheFollowingLinksToContinue": "Devam etmek için linkleri kullanabilirsiniz", + "FrameworkHomePage": "Framework ana sayfa", + "FrameworkDocumentation": "Framework Dokümantasyon", + "OfficialBlog": "Resmi blog", + "CommercialHomePage": "Kurumsal ana sayfa", + "CommercialSupportWebSite": "Kurumsal destek web sitesi" } } \ No newline at end of file diff --git a/abp_io/AbpIoLocalization/AbpIoLocalization/Base/Localization/Resources/tr.json b/abp_io/AbpIoLocalization/AbpIoLocalization/Base/Localization/Resources/tr.json index 3d324c16c0..c5f0021489 100644 --- a/abp_io/AbpIoLocalization/AbpIoLocalization/Base/Localization/Resources/tr.json +++ b/abp_io/AbpIoLocalization/AbpIoLocalization/Base/Localization/Resources/tr.json @@ -8,7 +8,24 @@ "Volo.AbpIo.Domain:010008": "Maksimum izin verilen kullanıcı sayısı mevcut kullanıcı sayısından az olamaz!", "Volo.AbpIo.Domain:010009": "Maksimum izin verilen kullanıcı sayısı sıfırdan az olamaz!", "Volo.AbpIo.Domain:010010": "Maksimum mac adresi sayısı geçildi!", - "Volo.AbpIo.Domain:010011": "Bireysel lisans birden fazla geliştiriciye sahip olamaz!", - "Volo.AbpIo.Domain:010012": "Lisans, lisans süresi bittikten bir ay sonra uzatılamaz!", + "Volo.AbpIo.Domain:010011": "Bireysel lisans birden fazla geliştiriciye sahip olamaz!", + "Volo.AbpIo.Domain:010012": "Lisans, lisans süresi bittikten bir ay sonra uzatılamaz!", + "Volo.AbpIo.Domain:020001": "Bu NPM Paketi silinemiyor çünkü \"{NugetPackages}\" Nuget Paketleri bu pakete bağımlı.", + "Volo.AbpIo.Domain:020002": "Bu NPM Paketi silinemiyor çünkü \"{Modules}\" Modülleri bu paketi kullanıyor.", + "Volo.AbpIo.Domain:020003": "Bu NPM Paketi silinemiyor çünkü \"{Modules}\" Modülleri bu paketi kullanıyor ve \"{NugetPackages}\" Nuget Paketleri bu pakete bağımlı.", + "Volo.AbpIo.Domain:020004": "Bu Nuget Paketi silinemiyor çünkü \"{Modules}\" Modülleri bu paketi kullanıyor.", + "WantToLearn?": "Öğrenmek ister misin?", + "ReadyToGetStarted?": "Başlamaya hazır mısın?", + "JoinOurCommunity": "Topluluğumuza katılın", + "GetStartedUpper": " BAŞLAYIN", + "ForkMeOnGitHub": "Fork me on GitHub", + "Features": "Özellikler", + "GetStarted": "Başlayın", + "Documents": "Dokümanlar", + "Community": "Topluluk", + "ContributionGuide": "Katkı Rehberi", + "Blog": "Blog", + "Commercial": "Ticari", + "SeeDocuments": "Dokümanlara Göz Atın" } } \ No newline at end of file From 533d99c51ee58866ab73087a0962685f15bd2a4b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Wed, 17 Jun 2020 15:31:08 +0300 Subject: [PATCH 22/92] Complete the event bus nav items --- docs/en/docs-nav.json | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/docs/en/docs-nav.json b/docs/en/docs-nav.json index c49b053ce0..6c3b3a94de 100644 --- a/docs/en/docs-nav.json +++ b/docs/en/docs-nav.json @@ -153,16 +153,23 @@ ] }, { - "text": "Events", + "text": "Event Bus", "items": [ { - "text": "Event Bus (local)" + "text": "Overall", + "path": "Event-Bus.md" + }, + { + "text": "Local Event Bus", + "path": "Local-Event-Bus.md" }, { "text": "Distributed Event Bus", + "path": "Distributed-Event-Bus.md", "items": [ { - "text": "RabbitMQ Integration" + "text": "RabbitMQ Integration", + "path": "Distributed-Event-Bus-RabbitMQ-Integration.md" } ] } From b614033568165204a4580d633d219b26f0d98019 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Wed, 17 Jun 2020 15:52:58 +0300 Subject: [PATCH 23/92] Event Bus Overall document --- docs/en/Event-Bus.md | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/docs/en/Event-Bus.md b/docs/en/Event-Bus.md index cf2b57c018..a0e6859465 100644 --- a/docs/en/Event-Bus.md +++ b/docs/en/Event-Bus.md @@ -1,3 +1,10 @@ # Event Bus -TODO \ No newline at end of file +An event bus is a mediator that transfers a message from a sender to a receiver. In this way, it provides a loosely coupled communication way between objects, services and applications. + +## Event Bus Types + +ABP Framework provides two type of event buses; + +* **[Local Event Bus](Local-Event-Bus.md)** is suitable for in-process messaging. +* **[Distributed Event Bus](Distributed-Event-Bus.md)** is suitable for inter-process messaging, like microservices publishing and subscribing to distributed events. \ No newline at end of file From c728d70f5ea09431194880468865022c99d60c16 Mon Sep 17 00:00:00 2001 From: Yunus Emre Kalkan Date: Wed, 17 Jun 2020 16:17:06 +0300 Subject: [PATCH 24/92] Update tr.json --- .../AbpIoLocalization/Base/Localization/Resources/tr.json | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/abp_io/AbpIoLocalization/AbpIoLocalization/Base/Localization/Resources/tr.json b/abp_io/AbpIoLocalization/AbpIoLocalization/Base/Localization/Resources/tr.json index c5f0021489..767346cd5e 100644 --- a/abp_io/AbpIoLocalization/AbpIoLocalization/Base/Localization/Resources/tr.json +++ b/abp_io/AbpIoLocalization/AbpIoLocalization/Base/Localization/Resources/tr.json @@ -17,15 +17,13 @@ "WantToLearn?": "Öğrenmek ister misin?", "ReadyToGetStarted?": "Başlamaya hazır mısın?", "JoinOurCommunity": "Topluluğumuza katılın", - "GetStartedUpper": " BAŞLAYIN", - "ForkMeOnGitHub": "Fork me on GitHub", + "GetStartedUpper": "BAŞLAYIN", "Features": "Özellikler", "GetStarted": "Başlayın", "Documents": "Dokümanlar", "Community": "Topluluk", "ContributionGuide": "Katkı Rehberi", "Blog": "Blog", - "Commercial": "Ticari", "SeeDocuments": "Dokümanlara Göz Atın" } -} \ No newline at end of file +} From 9b9984c5d6376c0eb1e529a747418387d85dc3c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Wed, 17 Jun 2020 16:42:37 +0300 Subject: [PATCH 25/92] Update Local-Event-Bus.md --- docs/en/Local-Event-Bus.md | 164 +++++++++++++++++++++++++++++++++++++ 1 file changed, 164 insertions(+) diff --git a/docs/en/Local-Event-Bus.md b/docs/en/Local-Event-Bus.md index 859ec8d28e..62b12c8457 100644 --- a/docs/en/Local-Event-Bus.md +++ b/docs/en/Local-Event-Bus.md @@ -1,3 +1,167 @@ # Local Event Bus +The Local Event Bus allows services to publish and subscribe to **in-process events**. That means it is suitable if two services (publisher and subscriber) are running in the same process. + +## Publishing Events + +There are two ways of publishing local events explained in the following sections. + +### ILocalEventBus + +`ILocalEventBus` can be [injected](Dependency-Injection.md) and used to publish a local event. + +**Example: Publish an event when the stock count of a product changes** + +````csharp +using System; +using System.Threading.Tasks; +using Volo.Abp.DependencyInjection; +using Volo.Abp.EventBus.Local; + +namespace AbpDemo +{ + public class MyService : ITransientDependency + { + private readonly ILocalEventBus _localEventBus; + + public MyService(ILocalEventBus localEventBus) + { + _localEventBus = localEventBus; + } + + public virtual async Task ChangeStockCountAsync(Guid productId, int newCount) + { + //TODO: IMPLEMENT YOUR LOGIC... + + //PUBLISH THE EVENT + await _localEventBus.PublishAsync( + new StockCountChangedEvent + { + ProductId = productId, + NewCount = newCount + } + ); + } + } +} +```` + +`PublishAsync` method gets a single parameter: the event object, which is responsible to hold the data related to the event. It is a simple plain class: + +````csharp +using System; + +namespace AbpDemo +{ + public class StockCountChangedEvent + { + public Guid ProductId { get; set; } + + public int NewCount { get; set; } + } +} +```` + +Even if you don't need to transfer any data, you need to create a class (which is an empty class in this case). + +### Inside Entity / Aggregate Root Classes + +[Entities](Entities.md) can not inject services via dependency injection, but it is very common to publish events inside entity / aggregate root classes. + +**Example: Publish an event inside an aggregate root method** + +````csharp +using System; +using Volo.Abp.Domain.Entities; + +namespace AbpDemo +{ + public class Product : AggregateRoot + { + public string Name { get; set; } + + public int StockCount { get; private set; } + + private Product() { } + + public Product(Guid id, string name) + : base(id) + { + Name = name; + } + + public void ChangeStockCount(int newCount) + { + StockCount = newCount; + + //ADD an EVENT TO BE PUBLISHED + AddLocalEvent( + new StockCountChangedEvent + { + ProductId = Id, + NewCount = newCount + } + ); + } + } +} +```` + +`AggregateRoot` class defines the `AddLocalEvent` to add a new local event, that is published when the aggregate root object is saved (created or updated) into the database. + +> If an entity publishes such an event, it is a good practice to change the related properties in a controlled manner, just like the example above - `StockCount` can only be changed by the `ChangeStockCount` which guarantees publishing the event. + +#### IGeneratesDomainEvents Interface + +Actually, adding local events are not unique to the `AggregateRoot` class. You can implement `IGeneratesDomainEvents` for any entity class. But, `AggregateRoot` implements it by default and makes it easy for you. + +> It is not suggested to implement this interface for entities those are not aggregate roots, since it may not work for some database providers for such entities. It works for EF Core, but not works for MongoDB for example. + +#### How It Was Implemented? + +Calling the `AddLocalEvent` doesn't immediately publish the event. The event is published when you save changes to the database; + +* For EF Core, it is published on `DbContext.SaveChanges`. +* For MongoDB, it is published when you call repository's `InsertAsync` or `UpdateAsync` methods (since MongoDB has no such a change tracking system). + +## Subscribing to Events + +A service can implement the `ILocalEventHandler` to handle the event. + +**Example: Handle the `StockCountChangedEvent` defined above** + +````csharp +using System.Threading.Tasks; +using Volo.Abp.DependencyInjection; +using Volo.Abp.EventBus; + +namespace AbpDemo +{ + public class MyHandler + : ILocalEventHandler, + ITransientDependency + { + public async Task HandleEventAsync(StockCountChangedEvent eventData) + { + //TODO: your code that does somthing on the event + } + } +} +```` + +That's all. `MyHandler` is **automatically discovered** by the ABP Framework and `HandleEventAsync` is called whenever a `StockCountChangedEvent` occurs. You can inject any service and perform any required logic here. + +There can be zero or more handlers subscribed to the same event. + +> The handler class must be registered to the dependency injection (DI). The sample above uses the `ITransientDependency` to accomplish it. See the [DI document](Dependency-Injection.md) for more options. + +## Transaction & Exception Behavior + +When an event published, subscribed event handlers are immediately executed. So; + +* If a handler **throws an exception**, it effects the code that published the event. That means it gets the exception on the `PublishAsync` call. So, **use try-catch yourself** in the event handler if you want to hide the error. +* If the event publishing code is being executed inside a [Unit Of Work](Unit-Of-Work.md) scope, the event handlers also covered by the unit of work. That means if your UOW is transactional and a handler throws an exception, the transaction is rolled back. + +## Pre-Built Events + TODO \ No newline at end of file From 47eb121d00f1b6df0af753205fb2f93140f0efa9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Wed, 17 Jun 2020 17:28:27 +0300 Subject: [PATCH 26/92] Complete the local event bus document. --- docs/en/Local-Event-Bus.md | 66 ++++++++++++++++++++++++++++++++++++-- 1 file changed, 63 insertions(+), 3 deletions(-) diff --git a/docs/en/Local-Event-Bus.md b/docs/en/Local-Event-Bus.md index 62b12c8457..3aa9fd7d07 100644 --- a/docs/en/Local-Event-Bus.md +++ b/docs/en/Local-Event-Bus.md @@ -122,7 +122,7 @@ Actually, adding local events are not unique to the `AggregateRoot` class. You c Calling the `AddLocalEvent` doesn't immediately publish the event. The event is published when you save changes to the database; * For EF Core, it is published on `DbContext.SaveChanges`. -* For MongoDB, it is published when you call repository's `InsertAsync` or `UpdateAsync` methods (since MongoDB has no such a change tracking system). +* For MongoDB, it is published when you call repository's `InsertAsync`, `UpdateAsync` or `DeleteAsync` methods (since MongoDB has not a change tracking system). ## Subscribing to Events @@ -151,7 +151,8 @@ namespace AbpDemo That's all. `MyHandler` is **automatically discovered** by the ABP Framework and `HandleEventAsync` is called whenever a `StockCountChangedEvent` occurs. You can inject any service and perform any required logic here. -There can be zero or more handlers subscribed to the same event. +* **Zero or more handlers** can subscribe to the same event. +* A single event handler class can **subscribe to multiple events** but implementing the `ILocalEventHandler` interface for each event type. > The handler class must be registered to the dependency injection (DI). The sample above uses the `ITransientDependency` to accomplish it. See the [DI document](Dependency-Injection.md) for more options. @@ -164,4 +165,63 @@ When an event published, subscribed event handlers are immediately executed. So; ## Pre-Built Events -TODO \ No newline at end of file +It is very common to **publish events on entity create, update and delete** operations. ABP Framework **automatically** publish these events for all entities. You can just subscribe to the related event. + +**Example: Subscribe to an event that published when a user was created** + +````csharp +using System.Threading.Tasks; +using Microsoft.AspNetCore.Identity; +using Volo.Abp.DependencyInjection; +using Volo.Abp.Domain.Entities.Events; +using Volo.Abp.EventBus; + +namespace AbpDemo +{ + public class MyHandler + : ILocalEventHandler>, + ITransientDependency + { + public async Task HandleEventAsync( + EntityCreatedEventData eventData) + { + var userName = eventData.Entity.UserName; + var email = eventData.Entity.Email; + //... + } + } +} +```` + +This class subscribes to the `EntityCreatedEventData`, which is published just after a user was created. You may want to send a "Welcome" email to the new user. + +There are two types of these events: events with past tense and events with continuous tense. + +### Events with Past Tense + +Events with past tense are published when the related unit of work completed and the entity change successfully saved to the database. If you throw an exception on these event handlers, it **can not rollback** the transaction since it was already committed. + +The event types are; + +* `EntityCreatedEventData` is published just after an entity was successfully created. +* `EntityUpdatedEventData` is published just after an entity was successfully updated. +* `EntityDeletedEventData` is published just after an entity was successfully deleted. +* `EntityChangedEventData` is published just after an entity was successfully created, updated or deleted. It can be a shortcut if you need to listen any type of change - instead of subscribing to the individual events. + +### Events with Continuous Tense + +Events with continuous tense are published before completing the transaction (if database transaction is supported by the database provider being used). If you throw an exception on these event handlers, it **can rollback** the transaction since it is not completed yet and the change is not saved to the database. + +The event types are; + +* `EntityCreatingEventData` is published just before saving a new entity to the database. +* `EntityUpdatingEventData` is published just before an existing entity is being updated. +* `EntityDeletingEventData` is published just before an entity is being deleted. +* `EntityChangingEventData` is published just before an entity is being created, updated or deleted. It can be a shortcut if you need to listen any type of change - instead of subscribing to the individual events. + +#### How It Was Implemented? + +Pre-build events are published when you save changes to the database; + +* For EF Core, they are published on `DbContext.SaveChanges`. +* For MongoDB, they are published when you call repository's `InsertAsync`, `UpdateAsync` or `DeleteAsync` methods (since MongoDB has not a change tracking system). \ No newline at end of file From 7f1c5e4ed0e6bac581b44a8862e02643b1249f9b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Wed, 17 Jun 2020 17:30:52 +0300 Subject: [PATCH 27/92] Update Local-Event-Bus.md --- docs/en/Local-Event-Bus.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/en/Local-Event-Bus.md b/docs/en/Local-Event-Bus.md index 3aa9fd7d07..4452149f56 100644 --- a/docs/en/Local-Event-Bus.md +++ b/docs/en/Local-Event-Bus.md @@ -109,7 +109,7 @@ namespace AbpDemo `AggregateRoot` class defines the `AddLocalEvent` to add a new local event, that is published when the aggregate root object is saved (created or updated) into the database. -> If an entity publishes such an event, it is a good practice to change the related properties in a controlled manner, just like the example above - `StockCount` can only be changed by the `ChangeStockCount` which guarantees publishing the event. +> If an entity publishes such an event, it is a good practice to change the related properties in a controlled manner, just like the example above - `StockCount` can only be changed by the `ChangeStockCount` method which guarantees publishing the event. #### IGeneratesDomainEvents Interface From fbd888a787520555d3192ea2cb8cf6b17e3666e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Wed, 17 Jun 2020 18:01:18 +0300 Subject: [PATCH 28/92] Update Distributed-Event-Bus.md --- docs/en/Distributed-Event-Bus.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/docs/en/Distributed-Event-Bus.md b/docs/en/Distributed-Event-Bus.md index 03beb814d2..2120ac45a3 100644 --- a/docs/en/Distributed-Event-Bus.md +++ b/docs/en/Distributed-Event-Bus.md @@ -1,3 +1,14 @@ # Distributed Event Bus +Distributed Event bus system allows to publish and subscribe to events that can be transferred across application/service boundaries. You can use the distributed event bus to asynchronously send and receive message between microservices or applications. + +## Providers + +Distributed event bus system provides an abstraction that can be implemented by any vendor/provider. There are two providers implemented out of the box: + +* `LocalDistributedEventBus` is the default implementation that implements the distributed event bus to work as in-process. Yes! The **default implementation works just like the [local event bus](Local-Event-Bus.md)**, if you don't configure a real distributed provider. +* `RabbitMqDistributedEventBus` implements the distributed event bus with the [RabbitMQ](https://www.rabbitmq.com/). See the [RabbitMQ integration document](Distributed-Event-Bus-RabbitMQ-Integration.md) to learn how to configure it. + +## Publishing Events + TODO \ No newline at end of file From c72bcf48f44f2a1198b1284e0eb8623b62504f54 Mon Sep 17 00:00:00 2001 From: Arman Ozak Date: Wed, 17 Jun 2020 18:40:34 +0300 Subject: [PATCH 29/92] feat: add circular reference to parents of nodes --- .../core/src/lib/tests/tree-utils.spec.ts | 31 ++++++++++++------- .../packages/core/src/lib/utils/tree-utils.ts | 12 ++++--- 2 files changed, 28 insertions(+), 15 deletions(-) diff --git a/npm/ng-packs/packages/core/src/lib/tests/tree-utils.spec.ts b/npm/ng-packs/packages/core/src/lib/tests/tree-utils.spec.ts index 454c1c6891..6115114872 100644 --- a/npm/ng-packs/packages/core/src/lib/tests/tree-utils.spec.ts +++ b/npm/ng-packs/packages/core/src/lib/tests/tree-utils.spec.ts @@ -1,4 +1,4 @@ -import { createTreeFromList, TreeNode } from '../utils/tree-utils'; +import { BaseTreeNode, createTreeFromList, TreeNode } from '../utils/tree-utils'; const LIST_1 = [ { id: 1, pid: null }, @@ -46,23 +46,32 @@ describe('Tree Utils', () => { ${LIST_2} | ${TREE_2} ${LIST_3} | ${TREE_3} `('should return $expected when given $list', ({ list, expected }: TestCreateTreeFromList) => { - expect( - createTreeFromList( - list, - x => x.id, - x => x.pid, - ), - ).toEqual(expected); + const tree = createTreeFromList( + list, + x => x.id, + x => x.pid, + x => BaseTreeNode.create(x), + ); + + expect(removeParents(tree)).toEqual(expected); }); }); }); +function removeParents(tree: TreeNode[]) { + return tree.map(v => { + const { parent, ...node } = v; + node.children = removeParents(node.children); + return node; + }); +} + interface TestCreateTreeFromList { - list: ModelA[]; - expected: TreeNode[]; + list: Model[]; + expected: TreeNode[]; } -interface ModelA { +interface Model { id: 1; pid: null; } diff --git a/npm/ng-packs/packages/core/src/lib/utils/tree-utils.ts b/npm/ng-packs/packages/core/src/lib/utils/tree-utils.ts index 607a30ec52..d4c0ba0ab3 100644 --- a/npm/ng-packs/packages/core/src/lib/utils/tree-utils.ts +++ b/npm/ng-packs/packages/core/src/lib/utils/tree-utils.ts @@ -1,19 +1,20 @@ -export class TreeNodeFactory { +export class BaseTreeNode { children: TreeNode[] = []; isLeaf = true; + parent: TreeNode; constructor(props: T) { Object.assign(this, props); } static create(props: T) { - return new TreeNodeFactory(props) as TreeNode; + return new BaseTreeNode(props) as TreeNode; } } export function createTreeFromList( list: T[], - keySelector: (item: T) => number | string | Symbol, + keySelector: (item: T) => number | string | symbol, parentKeySelector: typeof keySelector, valueMapper: (item: T) => R, ) { @@ -27,8 +28,10 @@ export function createTreeFromList( if (parentId) { const parent = map.get(parentId); + if (!parent) return; (parent as any).children.push(node); (parent as any).isLeaf = false; + (node as any).parent = parent; } else { tree.push(node); } @@ -39,7 +42,7 @@ export function createTreeFromList( export function createMapFromList( list: T[], - keySelector: (item: T) => number | string | Symbol, + keySelector: (item: T) => number | string | symbol, valueMapper: (item: T) => R, ) { type Key = ReturnType; @@ -54,6 +57,7 @@ export type TreeNode = { } & { children: TreeNode[]; isLeaf: boolean; + parent?: TreeNode; }; type NodeValue any> = F extends undefined From 6478f57317473b381f8909834314e2f5c29aa2be Mon Sep 17 00:00:00 2001 From: Arman Ozak Date: Wed, 17 Jun 2020 18:42:15 +0300 Subject: [PATCH 30/92] feat: add node, nav, and tab types --- .../packages/core/src/lib/models/common.ts | 22 +++++++++++-------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/npm/ng-packs/packages/core/src/lib/models/common.ts b/npm/ng-packs/packages/core/src/lib/models/common.ts index 773233857f..ee160fef6d 100644 --- a/npm/ng-packs/packages/core/src/lib/models/common.ts +++ b/npm/ng-packs/packages/core/src/lib/models/common.ts @@ -1,4 +1,4 @@ -import { EventEmitter } from '@angular/core'; +import { EventEmitter, Type } from '@angular/core'; import { Router } from '@angular/router'; import { Subject } from 'rxjs'; import { eLayoutType } from '../enums/common'; @@ -35,21 +35,25 @@ export namespace ABP { maxResultCount?: number; } - export interface Route { - children?: Route[]; - invisible?: boolean; - layout?: eLayoutType; + export interface Node { name: string; - order?: number; parentName?: string; + order?: number; + invisible?: boolean; + } + + export interface Nav extends Node { path: string; requiredPolicy?: string; + } + + export interface Route extends Nav { + layout?: eLayoutType; iconClass?: string; } - export interface FullRoute extends Route { - url?: string; - wrapper?: boolean; + export interface Tab extends Nav { + component: Type; } export interface BasicItem { From 41f1f2740e074ff840679431d7f58653a97d4aee Mon Sep 17 00:00:00 2001 From: Arman Ozak Date: Wed, 17 Jun 2020 18:43:30 +0300 Subject: [PATCH 31/92] feat: add utility function for getting a route path --- .../core/src/lib/tests/route-utils.spec.ts | 60 +++++++++++++ .../core/src/lib/utils/route-utils.ts | 87 ++----------------- 2 files changed, 65 insertions(+), 82 deletions(-) create mode 100644 npm/ng-packs/packages/core/src/lib/tests/route-utils.spec.ts diff --git a/npm/ng-packs/packages/core/src/lib/tests/route-utils.spec.ts b/npm/ng-packs/packages/core/src/lib/tests/route-utils.spec.ts new file mode 100644 index 0000000000..9c5f81ac2a --- /dev/null +++ b/npm/ng-packs/packages/core/src/lib/tests/route-utils.spec.ts @@ -0,0 +1,60 @@ +import { Component } from '@angular/core'; +import { RouterModule } from '@angular/router'; +import { createRoutingFactory, SpectatorRouting } from '@ngneat/spectator/jest'; +import { RouterOutletComponent } from '../components'; +import { getRoutePath } from '../utils/route-utils'; + +// tslint:disable-next-line +@Component({ template: '' }) +class DummyComponent {} + +describe('Route Utils', () => { + describe('#getRoutePath', () => { + let spectator: SpectatorRouting; + const createRouting = createRoutingFactory({ + component: RouterOutletComponent, + stubsEnabled: false, + declarations: [DummyComponent], + imports: [RouterModule], + routes: [ + { + path: '', + children: [ + { + path: 'foo', + children: [ + { + path: 'bar', + children: [ + { + path: 'baz', + component: DummyComponent, + }, + ], + }, + ], + }, + ], + }, + ], + }); + + beforeEach(async () => { + spectator = createRouting(); + }); + + test.each` + url | expected + ${''} | ${'/'} + ${'/'} | ${'/'} + ${'/foo'} | ${'/foo'} + ${'/foo/bar'} | ${'/foo/bar'} + ${'/foo/bar/baz'} | ${'/foo/bar/baz'} + ${'/foo?bar=baz'} | ${'/foo'} + ${'/foo#bar'} | ${'/foo'} + `('should return $expected when url is $url', async ({ url, expected }) => { + await spectator.router.navigateByUrl(url); + expect(getRoutePath(spectator.router)).toBe(expected); + }); + }); +}); diff --git a/npm/ng-packs/packages/core/src/lib/utils/route-utils.ts b/npm/ng-packs/packages/core/src/lib/utils/route-utils.ts index 51abe4218b..7ef062986a 100644 --- a/npm/ng-packs/packages/core/src/lib/utils/route-utils.ts +++ b/npm/ng-packs/packages/core/src/lib/utils/route-utils.ts @@ -1,85 +1,8 @@ -import { ABP } from '../models/common'; +import { PRIMARY_OUTLET, Router, UrlSegmentGroup } from '@angular/router'; -export function organizeRoutes( - routes: ABP.FullRoute[], - wrappers: ABP.FullRoute[] = [], - parentNameArr = [] as ABP.FullRoute[], - parentName: string = null, -): ABP.FullRoute[] { - const filter = route => { - if (route.children && route.children.length) { - route.children = organizeRoutes(route.children, wrappers, parentNameArr, route.name); - } +export function getRoutePath(router: Router) { + const emptyGroup = { segments: [] } as UrlSegmentGroup; + const primaryGroup = router.parseUrl(router.url).root.children[PRIMARY_OUTLET]; - if (route.parentName && route.parentName !== parentName) { - parentNameArr.push(route); - return false; - } - - return true; - }; - - if (parentName) { - // recursive block - return routes.filter(filter); - } - - const filteredRoutes = routes.filter(filter); - - if (parentNameArr.length) { - return sortRoutes(setChildRoute([...filteredRoutes, ...wrappers], parentNameArr)); - } - - return filteredRoutes; -} - -export function setChildRoute( - routes: ABP.FullRoute[], - parentNameArr: ABP.FullRoute[], -): ABP.FullRoute[] { - return routes.map(route => { - if (route.children && route.children.length) { - route.children = setChildRoute(route.children, parentNameArr); - } - - const foundedChildren = parentNameArr.filter(parent => parent.parentName === route.name); - if (foundedChildren && foundedChildren.length) { - route.children = [...(route.children || []), ...foundedChildren]; - } - - return route; - }); -} - -export function sortRoutes(routes: ABP.FullRoute[] = []): ABP.FullRoute[] { - if (!routes.length) return []; - return routes - .map((route, index) => { - return { - ...route, - order: typeof route.order === 'undefined' ? index + 1 : route.order, - }; - }) - .sort((a, b) => a.order - b.order) - .map(route => { - if (route.children && route.children.length) { - route.children = sortRoutes(route.children); - } - - return route; - }); -} - -const ABP_ROUTES = [] as ABP.FullRoute[]; - -export function addAbpRoutes(routes: ABP.FullRoute | ABP.FullRoute[]): void { - if (!Array.isArray(routes)) { - routes = [routes]; - } - - ABP_ROUTES.push(...routes); -} - -export function getAbpRoutes(): ABP.FullRoute[] { - return ABP_ROUTES; + return '/' + (primaryGroup || emptyGroup).segments.map(({ path }) => path).join('/'); } From 19a405f3f7f721667750dc907039b6914405968c Mon Sep 17 00:00:00 2001 From: Arman Ozak Date: Wed, 17 Jun 2020 18:44:43 +0300 Subject: [PATCH 32/92] feat: add a service for routes and setting tabs --- .../packages/core/src/lib/services/index.ts | 1 + .../core/src/lib/services/routes.service.ts | 144 ++++++++++++++++++ .../core/src/lib/tests/routes.service.spec.ts | 113 ++++++++++++++ 3 files changed, 258 insertions(+) create mode 100644 npm/ng-packs/packages/core/src/lib/services/routes.service.ts create mode 100644 npm/ng-packs/packages/core/src/lib/tests/routes.service.spec.ts diff --git a/npm/ng-packs/packages/core/src/lib/services/index.ts b/npm/ng-packs/packages/core/src/lib/services/index.ts index f01dc876de..064dcbd35e 100644 --- a/npm/ng-packs/packages/core/src/lib/services/index.ts +++ b/npm/ng-packs/packages/core/src/lib/services/index.ts @@ -9,5 +9,6 @@ export * from './localization.service'; export * from './profile-state.service'; export * from './profile.service'; export * from './rest.service'; +export * from './routes.service'; export * from './session-state.service'; export * from './track-by.service'; diff --git a/npm/ng-packs/packages/core/src/lib/services/routes.service.ts b/npm/ng-packs/packages/core/src/lib/services/routes.service.ts new file mode 100644 index 0000000000..4b9f3e7899 --- /dev/null +++ b/npm/ng-packs/packages/core/src/lib/services/routes.service.ts @@ -0,0 +1,144 @@ +import { Injectable } from '@angular/core'; +import { Store } from '@ngxs/store'; +import { BehaviorSubject, Observable } from 'rxjs'; +import { ABP } from '../models/common'; +import { ConfigState } from '../states/config.state'; +import { pushValueTo } from '../utils/array-utils'; +import { BaseTreeNode, createTreeFromList, TreeNode } from '../utils/tree-utils'; + +export abstract class AbstractTreeService { + abstract id: string; + abstract parentId: string; + abstract hide: (item: T) => boolean; + abstract sort: (a: T, b: T) => number; + + private _flat$ = new BehaviorSubject([]); + private _tree$ = new BehaviorSubject[]>([]); + private _visible$ = new BehaviorSubject[]>([]); + + get flat(): T[] { + return this._flat$.value; + } + + get flat$(): Observable { + return this._flat$.asObservable(); + } + + get tree(): TreeNode[] { + return this._tree$.value; + } + + get tree$(): Observable[]> { + return this._tree$.asObservable(); + } + + get visible(): TreeNode[] { + return this._visible$.value; + } + + get visible$(): Observable[]> { + return this._visible$.asObservable(); + } + + protected createTree(items: T[]): TreeNode[] { + return createTreeFromList>( + items, + item => item[this.id], + item => item[this.parentId], + item => BaseTreeNode.create(item), + ); + } + + private filterWith(setOrMap: Set | Map): T[] { + return this._flat$.value.filter( + item => !setOrMap.has(item[this.id]) && !setOrMap.has(item[this.parentId]), + ); + } + + private publish(flatItems: T[], visibleItems: T[]): T[] { + this._flat$.next(flatItems); + this._tree$.next(this.createTree(flatItems)); + this._visible$.next(this.createTree(visibleItems)); + return flatItems; + } + + add(items: T[]): T[] { + const map = new Map(); + items.forEach(item => map.set(item[this.id], item)); + + const flatItems = this.filterWith(map); + map.forEach(pushValueTo(flatItems)); + + flatItems.sort(this.sort); + const visibleItems = flatItems.filter(item => !this.hide(item)); + + return this.publish(flatItems, visibleItems); + } + + patch(identifier: string, props: Partial): T[] | false { + const flatItems = this._flat$.value; + const index = flatItems.findIndex(item => item[this.id] === identifier); + if (index < 0) return false; + + flatItems[index] = { ...flatItems[index], ...props }; + + flatItems.sort(this.sort); + const visibleItems = flatItems.filter(item => !this.hide(item)); + + return this.publish(flatItems, visibleItems); + } + + remove(identifiers: string[]): T[] { + const set = new Set(); + identifiers.forEach(id => set.add(id)); + + const flatItems = this.filterWith(set); + const visibleItems = flatItems.filter(item => !this.hide(item)); + + return this.publish(flatItems, visibleItems); + } + + search(params: Partial, tree = this.tree): TreeNode { + const searchKeys = Object.keys(params); + + return tree.reduce( + (acc, node) => + acc + ? acc + : searchKeys.every(key => node[key] === params[key]) + ? node + : node.children + ? this.search(params, node.children) + : acc, + null, + ); + } +} + +@Injectable({ + providedIn: 'root', +}) +export class RoutesService extends AbstractTreeService { + readonly id = 'name'; + readonly parentId = 'parentName'; + readonly hide = (item: ABP.Route) => item.invisible; + readonly sort = (a: ABP.Route, b: ABP.Route) => a.order - b.order; +} + +@Injectable({ + providedIn: 'root', +}) +export class SettingTabsService extends AbstractTreeService { + readonly id = 'name'; + readonly parentId = 'parentName'; + readonly hide = (setting: ABP.Tab) => setting.invisible || !this.isGranted(setting); + readonly sort = (a: ABP.Tab, b: ABP.Tab) => a.order - b.order; + + constructor(private store: Store) { + super(); + } + + private isGranted(setting: ABP.Tab): boolean { + return this.store.selectSnapshot(ConfigState.getGrantedPolicy(setting.requiredPolicy)); + } +} diff --git a/npm/ng-packs/packages/core/src/lib/tests/routes.service.spec.ts b/npm/ng-packs/packages/core/src/lib/tests/routes.service.spec.ts new file mode 100644 index 0000000000..2392a86183 --- /dev/null +++ b/npm/ng-packs/packages/core/src/lib/tests/routes.service.spec.ts @@ -0,0 +1,113 @@ +import { take } from 'rxjs/operators'; +import { RoutesService } from '../services'; + +const routes = [ + { path: '/foo', name: 'foo' }, + { path: '/foo/bar', name: 'bar', parentName: 'foo', invisible: true, order: 2 }, + { path: '/foo/bar/baz', name: 'baz', parentName: 'bar', order: 1 }, + { path: '/foo/x', name: 'x', parentName: 'foo', order: 1 }, +]; + +describe('Routes Service', () => { + describe('#add', () => { + it('should add given routes as flat$, tree$, and visible$', async () => { + const service = new RoutesService(); + service.add(routes); + + const flat = await service.flat$.pipe(take(1)).toPromise(); + const tree = await service.tree$.pipe(take(1)).toPromise(); + const visible = await service.visible$.pipe(take(1)).toPromise(); + + expect(flat.length).toBe(4); + expect(flat[0].name).toBe('foo'); + expect(flat[1].name).toBe('baz'); + expect(flat[2].name).toBe('x'); + expect(flat[3].name).toBe('bar'); + + expect(tree.length).toBe(1); + expect(tree[0].name).toBe('foo'); + expect(tree[0].children.length).toBe(2); + expect(tree[0].children[0].name).toBe('x'); + expect(tree[0].children[1].name).toBe('bar'); + expect(tree[0].children[1].children[0].name).toBe('baz'); + + expect(visible.length).toBe(1); + expect(visible[0].name).toBe('foo'); + expect(visible[0].children.length).toBe(1); + expect(visible[0].children[0].name).toBe('x'); + }); + }); + + describe('#remove', () => { + it('should remove routes based on given routeNames', () => { + const service = new RoutesService(); + service.add(routes); + service.remove(['bar']); + + const flat = service.flat; + const tree = service.tree; + const visible = service.visible; + + expect(flat.length).toBe(2); + expect(flat[0].name).toBe('foo'); + expect(flat[1].name).toBe('x'); + + expect(tree.length).toBe(1); + expect(tree[0].name).toBe('foo'); + expect(tree[0].children.length).toBe(1); + expect(tree[0].children[0].name).toBe('x'); + + expect(visible.length).toBe(1); + expect(visible[0].name).toBe('foo'); + expect(visible[0].children.length).toBe(1); + expect(visible[0].children[0].name).toBe('x'); + }); + }); + + describe('#patch', () => { + it('should patch propeties of routes based on given routeNames', () => { + const service = new RoutesService(); + service.add(routes); + service.patch('x', { invisible: true }); + + const flat = service.flat; + const tree = service.tree; + const visible = service.visible; + + expect(flat.length).toBe(4); + expect(flat[0].name).toBe('foo'); + expect(flat[1].name).toBe('baz'); + expect(flat[2].name).toBe('x'); + expect(flat[3].name).toBe('bar'); + + expect(tree.length).toBe(1); + expect(tree[0].name).toBe('foo'); + expect(tree[0].children.length).toBe(2); + expect(tree[0].children[0].name).toBe('x'); + expect(tree[0].children[1].name).toBe('bar'); + expect(tree[0].children[1].children[0].name).toBe('baz'); + + expect(visible.length).toBe(1); + expect(visible[0].name).toBe('foo'); + expect(visible[0].children.length).toBe(0); + }); + + it('should return false when route name is not found', () => { + const service = new RoutesService(); + service.add(routes); + const result = service.patch('A man has no name.', { invisible: true }); + expect(result).toBe(false); + }); + }); + + describe('#search', () => { + it('should return node found when route name is not found', () => { + const service = new RoutesService(); + service.add(routes); + const result = service.search({ invisible: true }); + expect(result.name).toBe('bar'); + expect(result.children.length).toBe(1); + expect(result.children[0].name).toBe('baz'); + }); + }); +}); From 82188a0d2916f86fba427bea5b2b70666c827b2b Mon Sep 17 00:00:00 2001 From: Arman Ozak Date: Wed, 17 Jun 2020 18:53:37 +0300 Subject: [PATCH 33/92] feat: use RoutesService in DynamicLayoutComponent --- .../components/dynamic-layout.component.ts | 73 ++++++++----------- .../tests/dynamic-layout.component.spec.ts | 66 +++++++++-------- 2 files changed, 68 insertions(+), 71 deletions(-) diff --git a/npm/ng-packs/packages/core/src/lib/components/dynamic-layout.component.ts b/npm/ng-packs/packages/core/src/lib/components/dynamic-layout.component.ts index c07f11580f..5455187086 100644 --- a/npm/ng-packs/packages/core/src/lib/components/dynamic-layout.component.ts +++ b/npm/ng-packs/packages/core/src/lib/components/dynamic-layout.component.ts @@ -1,13 +1,14 @@ -import { Component, OnDestroy, Type } from '@angular/core'; -import { ActivatedRoute, NavigationEnd, Router, UrlSegment } from '@angular/router'; +import { Component, Injector, OnDestroy, Type } from '@angular/core'; +import { ActivatedRoute, NavigationEnd, Router } from '@angular/router'; import { Store } from '@ngxs/store'; -import snq from 'snq'; import { eLayoutType } from '../enums/common'; -import { ABP } from '../models/common'; +import { ABP } from '../models'; import { ReplaceableComponents } from '../models/replaceable-components'; -import { ConfigState } from '../states/config.state'; +import { RoutesService } from '../services/routes.service'; import { ReplaceableComponentsState } from '../states/replaceable-components.state'; +import { getRoutePath } from '../utils/route-utils'; import { takeUntilDestroy } from '../utils/rxjs-utils'; +import { TreeNode } from '../utils/tree-utils'; @Component({ selector: 'abp-dynamic-layout', @@ -22,23 +23,37 @@ import { takeUntilDestroy } from '../utils/rxjs-utils'; export class DynamicLayoutComponent implements OnDestroy { layout: Type; - constructor(private router: Router, private route: ActivatedRoute, private store: Store) { - const { routes } = this.store.selectSnapshot(ConfigState.getAll); + constructor( + injector: Injector, + private route: ActivatedRoute, + private routes: RoutesService, + private store: Store, + ) { + const router = injector.get(Router); + const layouts = { + application: this.getComponent('Theme.ApplicationLayoutComponent'), + account: this.getComponent('Theme.AccountLayoutComponent'), + empty: this.getComponent('Theme.EmptyLayoutComponent'), + }; router.events.pipe(takeUntilDestroy(this)).subscribe(event => { if (event instanceof NavigationEnd) { - const segments = snq(() => router.parseUrl(event.url).root.children.primary.segments, [ - { path: router.url.replace('/', '') }, - ] as any); + let expectedLayout = (this.route.snapshot.data || {}).layout; + const path = getRoutePath(router); - const layouts = { - application: this.getComponent('Theme.ApplicationLayoutComponent'), - account: this.getComponent('Theme.AccountLayoutComponent'), - empty: this.getComponent('Theme.EmptyLayoutComponent'), - }; + if (!expectedLayout) { + let node = { parent: this.routes.search({ path }) } as TreeNode; + while (node.parent) { + node = node.parent; - const expectedLayout = - (this.route.snapshot.data || {}).layout || findLayout(segments, routes); + if (node.layout) { + expectedLayout = node.layout; + break; + } + } + } + + if (!expectedLayout) expectedLayout = eLayoutType.empty; this.layout = layouts[expectedLayout].component; } @@ -51,27 +66,3 @@ export class DynamicLayoutComponent implements OnDestroy { ngOnDestroy() {} } - -function findLayout(segments: UrlSegment[], routes: ABP.FullRoute[]): eLayoutType { - let layout = eLayoutType.empty; - - const route = routes - .reduce((acc, val) => (val.wrapper ? [...acc, ...val.children] : [...acc, val]), []) - .find(r => r.path === segments[0].path); - - if (route) { - if (route.layout) { - layout = route.layout; - } - - if (route.children && route.children.length && segments.length > 1) { - const child = route.children.find(c => c.path === segments[1].path); - - if (child && child.layout) { - layout = child.layout; - } - } - } - - return layout; -} diff --git a/npm/ng-packs/packages/core/src/lib/tests/dynamic-layout.component.spec.ts b/npm/ng-packs/packages/core/src/lib/tests/dynamic-layout.component.spec.ts index e1a678a19f..89a6a683b9 100644 --- a/npm/ng-packs/packages/core/src/lib/tests/dynamic-layout.component.spec.ts +++ b/npm/ng-packs/packages/core/src/lib/tests/dynamic-layout.component.spec.ts @@ -1,13 +1,13 @@ +import { HttpClient } from '@angular/common/http'; import { Component, NgModule } from '@angular/core'; import { ActivatedRoute, RouterModule } from '@angular/router'; import { createRoutingFactory, SpectatorRouting } from '@ngneat/spectator/jest'; import { NgxsModule, Store } from '@ngxs/store'; import { DynamicLayoutComponent, RouterOutletComponent } from '../components'; -import { eLayoutType } from '../enums'; +import { eLayoutType } from '../enums/common'; import { ABP } from '../models'; -import { ConfigState, ReplaceableComponentsState } from '../states'; -import { ApplicationConfigurationService } from '../services'; -import { HttpClient } from '@angular/common/http'; +import { ApplicationConfigurationService, RoutesService } from '../services'; +import { ReplaceableComponentsState } from '../states'; @Component({ selector: 'abp-layout-application', @@ -48,28 +48,36 @@ class DummyComponent { constructor(public route: ActivatedRoute) {} } -const storeData = { - ConfigState: { - routes: [ - { - path: '', - wrapper: true, - children: [ - { - path: 'parentWithLayout', - layout: eLayoutType.application, - children: [ - { path: 'childWithoutLayout' }, - { path: 'childWithLayout', layout: eLayoutType.account }, - ], - }, - ], - }, - { path: 'withData', layout: eLayoutType.application }, - , - ] as ABP.FullRoute[], - environment: { application: {} }, +const routes: ABP.Route[] = [ + { + path: '', + name: 'Root', + }, + { + path: '/parentWithLayout', + name: 'ParentWithLayout', + parentName: 'Root', + layout: eLayoutType.application, + }, + { + path: '/parentWithLayout/childWithoutLayout', + name: 'ChildWithoutLayout', + parentName: 'ParentWithLayout', + }, + { + path: '/parentWithLayout/childWithLayout', + name: 'ChildWithLayout', + parentName: 'ParentWithLayout', + layout: eLayoutType.account, + }, + { + path: '/withData', + name: 'WithData', + layout: eLayoutType.application, }, +]; + +const storeData = { ReplaceableComponentsState: { replaceableComponents: [ { @@ -94,11 +102,7 @@ describe('DynamicLayoutComponent', () => { stubsEnabled: false, declarations: [DummyComponent, DynamicLayoutComponent], mocks: [ApplicationConfigurationService, HttpClient], - imports: [ - RouterModule, - DummyLayoutModule, - NgxsModule.forRoot([ConfigState, ReplaceableComponentsState]), - ], + imports: [RouterModule, DummyLayoutModule, NgxsModule.forRoot([ReplaceableComponentsState])], routes: [ { path: '', component: RouterOutletComponent }, { @@ -150,6 +154,8 @@ describe('DynamicLayoutComponent', () => { beforeEach(async () => { spectator = createComponent(); store = spectator.get(Store); + const routesService = spectator.get(RoutesService); + routesService.add(routes); store.reset(storeData); }); From 9c0fe4c8e145ef1519fa8dadf54adedc999325af Mon Sep 17 00:00:00 2001 From: Arman Ozak Date: Wed, 17 Jun 2020 18:55:22 +0300 Subject: [PATCH 34/92] refactor: use ABP.Route type instead of ABP.FullRoute --- .../nav-items/nav-items.component.ts | 42 +++++++++---------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/npm/ng-packs/packages/theme-basic/src/lib/components/nav-items/nav-items.component.ts b/npm/ng-packs/packages/theme-basic/src/lib/components/nav-items/nav-items.component.ts index 7bae923989..6819807cd7 100644 --- a/npm/ng-packs/packages/theme-basic/src/lib/components/nav-items/nav-items.component.ts +++ b/npm/ng-packs/packages/theme-basic/src/lib/components/nav-items/nav-items.component.ts @@ -1,31 +1,31 @@ -import { - Component, - AfterViewInit, - TrackByFunction, - TemplateRef, - ViewChild, - OnDestroy, - Input, -} from '@angular/core'; import { ABP, - takeUntilDestroy, - SetLanguage, + ApplicationConfiguration, AuthService, ConfigState, - ApplicationConfiguration, SessionState, + SetLanguage, + takeUntilDestroy, } from '@abp/ng.core'; -import { LayoutState } from '../../states/layout.state'; -import { Store, Select } from '@ngxs/store'; -import { eNavigationElementNames } from '../../enums/navigation-element-names'; -import { AddNavigationElement } from '../../actions/layout.actions'; -import { map, filter } from 'rxjs/operators'; -import { Observable } from 'rxjs'; -import { Layout } from '../../models/layout'; +import { + AfterViewInit, + Component, + Input, + OnDestroy, + TemplateRef, + TrackByFunction, + ViewChild, +} from '@angular/core'; import { Navigate, RouterState } from '@ngxs/router-plugin'; -import snq from 'snq'; +import { Select, Store } from '@ngxs/store'; import compare from 'just-compare'; +import { Observable } from 'rxjs'; +import { filter, map } from 'rxjs/operators'; +import snq from 'snq'; +import { AddNavigationElement } from '../../actions/layout.actions'; +import { eNavigationElementNames } from '../../enums/navigation-element-names'; +import { Layout } from '../../models/layout'; +import { LayoutState } from '../../states/layout.state'; @Component({ selector: 'abp-nav-items', @@ -52,7 +52,7 @@ export class NavItemsComponent implements AfterViewInit, OnDestroy { rightPartElements: TemplateRef[] = []; - trackByFn: TrackByFunction = (_, element) => element; + trackByFn: TrackByFunction = (_, element) => element.name; get defaultLanguage$(): Observable { return this.languages$.pipe( From 962e636640f497348860e63429a218bc2076ea87 Mon Sep 17 00:00:00 2001 From: Arman Ozak Date: Wed, 17 Jun 2020 18:56:28 +0300 Subject: [PATCH 35/92] feat: use RoutesService in RoutesComponent --- .../components/routes/routes.component.html | 33 +++++++------------ .../lib/components/routes/routes.component.ts | 33 +++---------------- 2 files changed, 17 insertions(+), 49 deletions(-) diff --git a/npm/ng-packs/packages/theme-basic/src/lib/components/routes/routes.component.html b/npm/ng-packs/packages/theme-basic/src/lib/components/routes/routes.component.html index 6437233cb1..4d22ddc8d2 100644 --- a/npm/ng-packs/packages/theme-basic/src/lib/components/routes/routes.component.html +++ b/npm/ng-packs/packages/theme-basic/src/lib/components/routes/routes.component.html @@ -1,6 +1,6 @@
- - + + + + diff --git a/npm/ng-packs/packages/theme-basic/src/lib/components/routes/routes.component.ts b/npm/ng-packs/packages/theme-basic/src/lib/components/routes/routes.component.ts index 2ed204e0b0..8b73c8ee29 100644 --- a/npm/ng-packs/packages/theme-basic/src/lib/components/routes/routes.component.ts +++ b/npm/ng-packs/packages/theme-basic/src/lib/components/routes/routes.component.ts @@ -1,37 +1,14 @@ -import { ABP, ConfigState } from '@abp/ng.core'; -import { Component, Input, Renderer2, TrackByFunction } from '@angular/core'; -import { Select } from '@ngxs/store'; -import { Observable } from 'rxjs'; -import { map } from 'rxjs/operators'; +import { ABP, RoutesService, TreeNode } from '@abp/ng.core'; +import { Component, Input, TrackByFunction } from '@angular/core'; @Component({ selector: 'abp-routes', templateUrl: 'routes.component.html', }) export class RoutesComponent { - @Select(ConfigState.getOne('routes')) - routes$: Observable; + @Input() smallScreen: boolean; - @Input() - smallScreen: boolean; + trackByFn: TrackByFunction> = (_, item) => item.name; - get visibleRoutes$(): Observable { - return this.routes$.pipe(map(routes => getVisibleRoutes(routes))); - } - - trackByFn: TrackByFunction = (_, item) => item.name; - - constructor(private renderer: Renderer2) {} -} - -function getVisibleRoutes(routes: ABP.FullRoute[]) { - return routes.reduce((acc, val) => { - if (val.invisible) return acc; - - if (val.children && val.children.length) { - val.children = getVisibleRoutes(val.children); - } - - return [...acc, val]; - }, []); + constructor(public readonly routes: RoutesService) {} } From 8cd85a4d443defd1694c72776f08726c819fd597 Mon Sep 17 00:00:00 2001 From: Arman Ozak Date: Wed, 17 Jun 2020 18:57:30 +0300 Subject: [PATCH 36/92] feat: use RoutesService in BreadcrumbComponent --- .../breadcrumb/breadcrumb.component.html | 4 +- .../breadcrumb/breadcrumb.component.ts | 84 +++++++++---------- 2 files changed, 42 insertions(+), 46 deletions(-) diff --git a/npm/ng-packs/packages/theme-shared/src/lib/components/breadcrumb/breadcrumb.component.html b/npm/ng-packs/packages/theme-shared/src/lib/components/breadcrumb/breadcrumb.component.html index fa8f7df3da..da17512ea1 100644 --- a/npm/ng-packs/packages/theme-shared/src/lib/components/breadcrumb/breadcrumb.component.html +++ b/npm/ng-packs/packages/theme-shared/src/lib/components/breadcrumb/breadcrumb.component.html @@ -1,4 +1,4 @@ -