diff --git a/docs/en/Background-Workers.md b/docs/en/Background-Workers.md index 6206e2fbd1..06535cccfc 100644 --- a/docs/en/Background-Workers.md +++ b/docs/en/Background-Workers.md @@ -72,7 +72,7 @@ public class PassiveUserCheckerWorker : AsyncPeriodicBackgroundWorkerBase } ```` -* `AsyncPeriodicBackgroundWorkerBase` uses the `AbpTimer` (a thread-safe timer) object to determine **the period**. We can set its `Period` property in the constructor. +* `AsyncPeriodicBackgroundWorkerBase` uses the `AbpAsyncTimer` (a thread-safe timer) object to determine **the period**. We can set its `Period` property in the constructor. * It required to implement the `DoWorkAsync` method to **execute** the periodic work. * It is a good practice to **resolve dependencies** from the `PeriodicBackgroundWorkerContext` instead of constructor injection. Because `AsyncPeriodicBackgroundWorkerBase` uses a `IServiceScope` that is **disposed** when your work finishes. * `AsyncPeriodicBackgroundWorkerBase` **catches and logs exceptions** thrown by the `DoWorkAsync` method. diff --git a/docs/en/UI/Angular/Data-Table-Column-Extensions.md b/docs/en/UI/Angular/Data-Table-Column-Extensions.md index babb329feb..ca4320c203 100644 --- a/docs/en/UI/Angular/Data-Table-Column-Extensions.md +++ b/docs/en/UI/Angular/Data-Table-Column-Extensions.md @@ -220,6 +220,7 @@ type EntityPropOptions = { permission?: string; visible?: PropPredicate; columnVisible?: ColumnPredicate; + tooltip?: FormPropTooltip; }; ``` @@ -234,6 +235,7 @@ As you see, passing `type` and `name` is enough to create an entity prop. Here i - **permission** is the permission context which will be used to decide if a column for this entity prop should be displayed to the user or not. (_default:_ `undefined`) - **visible** is a predicate that will be used to decide if the cell content of this entity prop should be displayed on the table or not based on the data record. (_default:_ `() => true`) - **columnVisible** is a predicate that will be used to decide if the column of this entity prop should be displayed on the table or not. (_default:_ `() => true`) +- **tooltip** . is a tooltip for table column (_default:_ `undefined`) > Important Note: Do not use record in visibility predicates. First of all, the table header checks it too and the record will be `undefined`. Second, if some cells are displayed and others are not, the table will be broken. Use the `valueResolver` and render an empty cell when you need to hide a specific cell. > @@ -272,6 +274,7 @@ const options: EntityPropOptions = { const sessionStateService = getInjected(SessionStateService); return !sessionStateService.getTenant()?.isAvailable; // hide this column when the tenant is available. }, + tooltip: { text: 'AbpIdentity::EmailAddress_Tooltip', placement: 'top' } }; const prop = new EntityProp(options); diff --git a/docs/en/UI/Angular/Dynamic-Form-Extensions.md b/docs/en/UI/Angular/Dynamic-Form-Extensions.md index 52a1f6cb86..2fb73a6fac 100644 --- a/docs/en/UI/Angular/Dynamic-Form-Extensions.md +++ b/docs/en/UI/Angular/Dynamic-Form-Extensions.md @@ -163,6 +163,8 @@ type FormPropOptions = { options?: PropCallback[]>>; autocomplete?: string; isExtra? boolean; + formText?: string; + tooltip?: FormPropTooltip; }; ``` @@ -182,6 +184,8 @@ As you see, passing `type` and `name` is enough to create a form prop. Here is w - **options** is a callback that is called when a dropdown is needed. It must return an observable. (_default:_ `undefined`) - **autocomplete** will be set as the `autocomplete` attribute of the input for the field. Please check [possible values](https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/autocomplete#Values). (_default:_ `'off'`) - **isExtra** indicates this prop is an object extension. When `true`, the value of the field will be mapped from and to `extraProperties` of the entity. (_default:_ `undefined`) +- **formText** is the definition of the field. Placed under the field. (_default:_ `undefined`) +- **tooltip** is the tooltip for the field placed near of the label (_default:_ `undefined`) > Important Note: Do not use `record` property of `PropData` in create form predicates and callbacks, because it will be `undefined`. You can use it on edit form contributors though. @@ -235,7 +239,9 @@ const options: FormPropOptions = { }, autocomplete: 'off', isExtra: true, - template: undefined | Type // Custom angular component + template: undefined | Type, // Custom angular component + tooltip: { text: 'Default::MyPropName_Tooltip', placement: 'top' }, + formText: 'Default::MyPropName_Description', }; const prop = new FormProp(options); diff --git a/docs/en/UI/Angular/Entity-Action-Extensions.md b/docs/en/UI/Angular/Entity-Action-Extensions.md index b715479f12..ead1a39b72 100644 --- a/docs/en/UI/Angular/Entity-Action-Extensions.md +++ b/docs/en/UI/Angular/Entity-Action-Extensions.md @@ -311,6 +311,8 @@ type EntityActionOptions = { visible?: ActionPredicate, btnClass?: string, btnStyle?: string, + showOnlyIcon?: boolean, + tooltip?: FormPropTooltip; }; ``` @@ -323,7 +325,9 @@ As you see, passing `action` and `text` is enough to create an entity action. He - **visible** is a predicate that will be used to decide if the current record should have this grid action or not. (_default:_ `() => true`) - **btnClass** is the classes that will be applied to the button. (_default:_ `'btn btn-primary text-center'`) - **btnStyle** is the styles that will be applied to the button. (_default:_ `''`) - +- **showOnlyIcon** is shows only the icon itself. (_default:_ `false`) +- **tooltip** is only available in single entity action button. Adds an tooltip for button. (_default:_ undefined) + You may find a full example below. ### EntityAction\ @@ -342,6 +346,8 @@ const options: EntityActionOptions = { visible: data => data.record.isLockedOut, btnClass:'btn btn-warning text-center', btnStyle: '', //Adds inline style + showOnlyIcon: true, + tooltip: { text: 'AbpIdentity::Edit', placement: 'top' } }; const action = new EntityAction(options); diff --git a/docs/zh-Hans/Background-Workers.md b/docs/zh-Hans/Background-Workers.md index da1e9f9f54..b2cc2a1580 100644 --- a/docs/zh-Hans/Background-Workers.md +++ b/docs/zh-Hans/Background-Workers.md @@ -44,7 +44,7 @@ public class MyWorker : BackgroundWorkerBase public class PassiveUserCheckerWorker : AsyncPeriodicBackgroundWorkerBase { public PassiveUserCheckerWorker( - AbpTimer timer, + AbpAsyncTimer timer, IServiceScopeFactory serviceScopeFactory ) : base( timer, @@ -71,7 +71,7 @@ public class PassiveUserCheckerWorker : AsyncPeriodicBackgroundWorkerBase } ```` -* `AsyncPeriodicBackgroundWorkerBase` 使用 `AbpTimer`(线程安全定时器)对象来确定**时间段**. 我们可以在构造函数中设置了`Period` 属性. +* `AsyncPeriodicBackgroundWorkerBase` 使用 `AbpAsyncTimer`(线程安全定时器)对象来确定**时间段**. 我们可以在构造函数中设置了`Period` 属性. * 它需要实现 `DoWorkAsync` 方法**执行**定期任务. * 最好使用 `PeriodicBackgroundWorkerContext` **解析依赖** 而不是构造函数. 因为 `AsyncPeriodicBackgroundWorkerBase` 使用 `IServiceScope` 在你的任务执行结束时会对其 **disposed**. * `AsyncPeriodicBackgroundWorkerBase` **捕获并记录** 由 `DoWorkAsync` 方法抛出的 **异常**. diff --git a/framework/src/Volo.Abp.BlazoriseUI/AbpCrudPageBase.cs b/framework/src/Volo.Abp.BlazoriseUI/AbpCrudPageBase.cs index 58867d6e5d..a70c93f3ac 100644 --- a/framework/src/Volo.Abp.BlazoriseUI/AbpCrudPageBase.cs +++ b/framework/src/Volo.Abp.BlazoriseUI/AbpCrudPageBase.cs @@ -306,10 +306,12 @@ public abstract class AbpCrudPageBase< protected virtual async Task SearchEntitiesAsync() { + var currentPage = CurrentPage; CurrentPage = 1; - - await GetEntitiesAsync(); - + if (currentPage == 1) + { + await GetEntitiesAsync(); + } await InvokeAsync(StateHasChanged); } @@ -596,12 +598,12 @@ public abstract class AbpCrudPageBase< await SetEntityActionsAsync(); } - + protected virtual ValueTask SetEntityActionsAsync() { return ValueTask.CompletedTask; } - + private async ValueTask TrySetTableColumnsAsync() { if (IsDisposed) @@ -614,7 +616,7 @@ public abstract class AbpCrudPageBase< protected virtual ValueTask SetTableColumnsAsync() { - + return ValueTask.CompletedTask; } diff --git a/framework/src/Volo.Abp.MongoDB/Volo/Abp/Domain/Repositories/MongoDB/MongoDbRepository.cs b/framework/src/Volo.Abp.MongoDB/Volo/Abp/Domain/Repositories/MongoDB/MongoDbRepository.cs index 443d8f89d3..45629f76e5 100644 --- a/framework/src/Volo.Abp.MongoDB/Volo/Abp/Domain/Repositories/MongoDB/MongoDbRepository.cs +++ b/framework/src/Volo.Abp.MongoDB/Volo/Abp/Domain/Repositories/MongoDB/MongoDbRepository.cs @@ -507,11 +507,21 @@ public class MongoDbRepository var dbContext = await GetDbContextAsync(cancellationToken); var collection = dbContext.Collection(); - await collection.DeleteManyAsync( - dbContext.SessionHandle, - Builders.Filter.Where(predicate), - cancellationToken: cancellationToken - ); + if (dbContext.SessionHandle != null) + { + await collection.DeleteManyAsync( + dbContext.SessionHandle, + Builders.Filter.Where(predicate), + cancellationToken: cancellationToken + ); + } + else + { + await collection.DeleteManyAsync( + Builders.Filter.Where(predicate), + cancellationToken: cancellationToken + ); + } } [Obsolete("Use GetQueryableAsync method.")] diff --git a/modules/identity/src/Volo.Abp.Identity.Web/Pages/Identity/Users/CreateModal.cshtml b/modules/identity/src/Volo.Abp.Identity.Web/Pages/Identity/Users/CreateModal.cshtml index 2a4216d5d7..fdf33444de 100644 --- a/modules/identity/src/Volo.Abp.Identity.Web/Pages/Identity/Users/CreateModal.cshtml +++ b/modules/identity/src/Volo.Abp.Identity.Web/Pages/Identity/Users/CreateModal.cshtml @@ -21,7 +21,7 @@ -
+
@* TODO: Can we use dynamic form? *@ @@ -72,7 +72,7 @@
-
+
@for (var i = 0; i < Model.Roles.Length; i++) { var role = Model.Roles[i]; diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Blazor/Components/PermissionManagementModal.razor b/modules/permission-management/src/Volo.Abp.PermissionManagement.Blazor/Components/PermissionManagementModal.razor index 88d023a906..0e0b2aa480 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Blazor/Components/PermissionManagementModal.razor +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Blazor/Components/PermissionManagementModal.razor @@ -43,9 +43,6 @@ @foreach (var group in _groups) { -

@group.DisplayName

- - -
+
@@ -24,10 +24,8 @@ { var group = Model.Groups[i]; -

@group.DisplayName

-
-
+
@if (actionsTemplate || (actionList.length && hasAtLeastOnePermittedAction)) { diff --git a/npm/ng-packs/packages/components/extensible/src/lib/components/extensible-table/extensible-table.component.ts b/npm/ng-packs/packages/components/extensible/src/lib/components/extensible-table/extensible-table.component.ts index 5670ca7343..a65a3925b1 100644 --- a/npm/ng-packs/packages/components/extensible/src/lib/components/extensible-table/extensible-table.component.ts +++ b/npm/ng-packs/packages/components/extensible/src/lib/components/extensible-table/extensible-table.component.ts @@ -1,7 +1,6 @@ import { ABP, - ConfigStateService, - CoreModule, + ConfigStateService, getShortDateFormat, getShortDateShortTimeFormat, getShortTimeFormat, @@ -13,9 +12,7 @@ import { import { AsyncPipe, formatDate, - NgComponentOutlet, - NgFor, - NgIf, + NgComponentOutlet, NgTemplateOutlet, } from '@angular/common'; import { @@ -175,7 +172,32 @@ export class ExtensibleTableComponent implements OnChanges { ); } - ngOnChanges({ data }: SimpleChanges) { + setPage({ offset }) { + this.list.page = offset; + } + + ngOnChanges({ data, recordsTotal }: SimpleChanges) { + if (data?.currentValue.length < 1 && recordsTotal?.currentValue > 0) { + let maxPage = Math.floor(Number(recordsTotal?.currentValue / this.list.maxResultCount)); + + if(recordsTotal?.currentValue < this.list.maxResultCount) { + this.list.page = 0; + return; + } + + if (recordsTotal?.currentValue % this.list.maxResultCount === 0) { + maxPage -= 1; + } + + if (this.list.page < maxPage) { + this.list.page = this.list.page; + return; + } + + this.list.page = maxPage; + return; + } + if (!data?.currentValue) return; if (data.currentValue.length < 1) { diff --git a/npm/ng-packs/packages/components/extensible/src/lib/components/grid-actions/grid-actions.component.html b/npm/ng-packs/packages/components/extensible/src/lib/components/grid-actions/grid-actions.component.html index 70809a8975..4fcf67fcfc 100644 --- a/npm/ng-packs/packages/components/extensible/src/lib/components/grid-actions/grid-actions.component.html +++ b/npm/ng-packs/packages/components/extensible/src/lib/components/grid-actions/grid-actions.component.html @@ -53,16 +53,31 @@ @if (action.visible(data)) { - + @if (action.tooltip) { + + } @else { + + } } diff --git a/npm/ng-packs/packages/components/extensible/src/lib/components/grid-actions/grid-actions.component.ts b/npm/ng-packs/packages/components/extensible/src/lib/components/grid-actions/grid-actions.component.ts index 6937b93d8a..f74d36dd79 100644 --- a/npm/ng-packs/packages/components/extensible/src/lib/components/grid-actions/grid-actions.component.ts +++ b/npm/ng-packs/packages/components/extensible/src/lib/components/grid-actions/grid-actions.component.ts @@ -8,7 +8,7 @@ import { import { EntityAction, EntityActionList } from '../../models/entity-actions'; import { EXTENSIONS_ACTION_TYPE } from '../../tokens/extensions.token'; import { AbstractActionsComponent } from '../abstract-actions/abstract-actions.component'; -import { NgbDropdownModule } from '@ng-bootstrap/ng-bootstrap'; +import { NgbDropdownModule, NgbTooltipModule } from '@ng-bootstrap/ng-bootstrap'; import { LocalizationModule, PermissionDirective } from '@abp/ng.core'; import { EllipsisDirective } from '@abp/ng.theme.shared'; import { NgClass, NgTemplateOutlet } from '@angular/common'; @@ -23,6 +23,7 @@ import { NgClass, NgTemplateOutlet } from '@angular/common'; NgClass, LocalizationModule, NgTemplateOutlet, + NgbTooltipModule ], selector: 'abp-grid-actions', templateUrl: './grid-actions.component.html', diff --git a/npm/ng-packs/packages/components/extensible/src/lib/models/entity-actions.ts b/npm/ng-packs/packages/components/extensible/src/lib/models/entity-actions.ts index f49b6fcf2c..eb103cb1bd 100644 --- a/npm/ng-packs/packages/components/extensible/src/lib/models/entity-actions.ts +++ b/npm/ng-packs/packages/components/extensible/src/lib/models/entity-actions.ts @@ -8,6 +8,7 @@ import { Actions, ActionsFactory, } from './actions'; +import { FormPropTooltip } from './form-props'; export class EntityActionList extends ActionList> {} @@ -25,6 +26,7 @@ export class EntityAction extends Action { readonly btnClass?: string; readonly btnStyle?: string; readonly showOnlyIcon?: boolean; + readonly tooltip?: FormPropTooltip; constructor(options: EntityActionOptions) { super(options.permission || '', options.visible, options.action); @@ -33,6 +35,7 @@ export class EntityAction extends Action { this.btnClass = options.btnClass || 'btn btn-primary text-center'; this.btnStyle = options.btnStyle; this.showOnlyIcon = options.showOnlyIcon || false; + this.tooltip = options.tooltip; } static create(options: EntityActionOptions) { diff --git a/npm/ng-packs/packages/core/src/lib/services/list.service.ts b/npm/ng-packs/packages/core/src/lib/services/list.service.ts index 48996bade1..ef835e9fcd 100644 --- a/npm/ng-packs/packages/core/src/lib/services/list.service.ts +++ b/npm/ng-packs/packages/core/src/lib/services/list.service.ts @@ -150,7 +150,7 @@ export class ListService implements this._query$.next({ filter: this._filter || undefined, maxResultCount: this._maxResultCount, - skipCount: this._filter ? 0 : this._page * this._maxResultCount, + skipCount: this._page * this._maxResultCount, sorting: this._sortOrder ? `${this._sortKey} ${this._sortOrder}` : undefined, } as any as QueryParamsType); } diff --git a/npm/ng-packs/packages/theme-shared/src/lib/directives/ngx-datatable-list.directive.ts b/npm/ng-packs/packages/theme-shared/src/lib/directives/ngx-datatable-list.directive.ts index 9ce5d1d860..3769a3aa3d 100644 --- a/npm/ng-packs/packages/theme-shared/src/lib/directives/ngx-datatable-list.directive.ts +++ b/npm/ng-packs/packages/theme-shared/src/lib/directives/ngx-datatable-list.directive.ts @@ -53,14 +53,6 @@ export class NgxDatatableListDirective implements OnChanges, OnDestroy, OnInit { }; } - private subscribeToPage() { - const sub = this.table.page.subscribe(({ offset }) => { - this.list.page = offset; - this.table.offset = offset; - }); - this.subscription.add(sub); - } - private subscribeToSort() { const sub = this.table.sort.subscribe(({ sorts: [{ prop, dir }] }) => { if (prop === this.list.sortKey && this.list.sortOrder === 'desc') { @@ -101,7 +93,6 @@ export class NgxDatatableListDirective implements OnChanges, OnDestroy, OnInit { } ngOnInit() { - this.subscribeToPage(); this.subscribeToSort(); } }