diff --git a/ui-ngx/src/app/modules/home/components/alarm/alarm-table-config.ts b/ui-ngx/src/app/modules/home/components/alarm/alarm-table-config.ts index 2bff69a53e..9a200b8a3d 100644 --- a/ui-ngx/src/app/modules/home/components/alarm/alarm-table-config.ts +++ b/ui-ngx/src/app/modules/home/components/alarm/alarm-table-config.ts @@ -60,6 +60,7 @@ export class AlarmTableConfig extends EntityTableConfig this.loadDataOnInit = false; this.tableTitle = ''; this.useTimePageLink = true; + this.pageMode = false; this.defaultTimewindowInterval = historyInterval(DAY * 30); this.detailsPanelEnabled = false; this.selectionEnabled = false; diff --git a/ui-ngx/src/app/modules/home/components/audit-log/audit-log-table-config.ts b/ui-ngx/src/app/modules/home/components/audit-log/audit-log-table-config.ts index f5c928f275..2a5d339230 100644 --- a/ui-ngx/src/app/modules/home/components/audit-log/audit-log-table-config.ts +++ b/ui-ngx/src/app/modules/home/components/audit-log/audit-log-table-config.ts @@ -52,11 +52,13 @@ export class AuditLogTableConfig extends EntityTableConfig) { + private store: Store, + private route: ActivatedRoute) { } ngOnInit() { @@ -117,6 +119,7 @@ export class AuditLogTableComponent implements OnInit { } updateOnInit = true; } + const pageMode = !!this.route.snapshot.data.isPage; this.auditLogTableConfig = new AuditLogTableConfig( this.auditLogService, this.translate, @@ -126,7 +129,8 @@ export class AuditLogTableComponent implements OnInit { this.entityIdValue, this.userIdValue, this.customerIdValue, - updateOnInit + updateOnInit, + pageMode ); } diff --git a/ui-ngx/src/app/modules/home/components/entity/entities-table.component.ts b/ui-ngx/src/app/modules/home/components/entity/entities-table.component.ts index 4059479cca..48e8933f58 100644 --- a/ui-ngx/src/app/modules/home/components/entity/entities-table.component.ts +++ b/ui-ngx/src/app/modules/home/components/entity/entities-table.component.ts @@ -31,19 +31,20 @@ import { import { PageComponent } from '@shared/components/page.component'; import { Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; -import { MAX_SAFE_PAGE_SIZE, PageLink, TimePageLink } from '@shared/models/page/page-link'; +import { MAX_SAFE_PAGE_SIZE, PageLink, PageQueryParam, TimePageLink } from '@shared/models/page/page-link'; import { MatDialog } from '@angular/material/dialog'; import { MatPaginator } from '@angular/material/paginator'; -import { MatSort } from '@angular/material/sort'; +import { MatSort, SortDirection } from '@angular/material/sort'; import { EntitiesDataSource } from '@home/models/datasource/entity-datasource'; -import { catchError, debounceTime, distinctUntilChanged, map, tap } from 'rxjs/operators'; +import { catchError, debounceTime, distinctUntilChanged, map, skip, tap } from 'rxjs/operators'; import { Direction, SortOrder } from '@shared/models/page/sort-order'; import { forkJoin, fromEvent, merge, Observable, of, Subscription } from 'rxjs'; import { TranslateService } from '@ngx-translate/core'; import { BaseData, HasId } from '@shared/models/base-data'; -import { ActivatedRoute } from '@angular/router'; +import { ActivatedRoute, QueryParamsHandling, Router } from '@angular/router'; import { - CellActionDescriptor, CellActionDescriptorType, + CellActionDescriptor, + CellActionDescriptorType, EntityActionTableColumn, EntityColumn, EntityTableColumn, @@ -58,7 +59,7 @@ import { AddEntityDialogData, EntityAction } from '@home/models/entity/entity-co import { calculateIntervalStartEndTime, HistoryWindowType, Timewindow } from '@shared/models/time/time.models'; import { DomSanitizer, SafeHtml } from '@angular/platform-browser'; import { TbAnchorComponent } from '@shared/components/tb-anchor.component'; -import { isDefined, isUndefined } from '@core/utils'; +import { isDefined, isEmptyStr, isUndefined } from '@core/utils'; import { HasUUID } from '@shared/models/id/has-uuid'; import { ResizeObserver } from '@juggle/resize-observer'; import { hidePageSizePixelValue } from '@shared/models/constants'; @@ -100,6 +101,7 @@ export class EntitiesTableComponent extends PageComponent implements IEntitiesTa hidePageSize = false; pageSizeOptions; pageLink: PageLink; + pageMode = true; textSearchMode = false; timewindow: Timewindow; dataSource: EntitiesDataSource>; @@ -116,7 +118,6 @@ export class EntitiesTableComponent extends PageComponent implements IEntitiesTa @ViewChild(MatPaginator) paginator: MatPaginator; @ViewChild(MatSort) sort: MatSort; - private sortSubscription: Subscription; private updateDataSubscription: Subscription; private viewInited = false; @@ -129,6 +130,7 @@ export class EntitiesTableComponent extends PageComponent implements IEntitiesTa private dialogService: DialogService, private domSanitizer: DomSanitizer, private cd: ChangeDetectorRef, + private router: Router, private componentFactoryResolver: ComponentFactoryResolver, private elementRef: ElementRef) { super(store); @@ -212,16 +214,19 @@ export class EntitiesTableComponent extends PageComponent implements IEntitiesTa this.columnsUpdated(); + const routerQueryParams: PageQueryParam = this.route.snapshot.queryParams; + let sortOrder: SortOrder = null; - if (this.entitiesTableConfig.defaultSortOrder) { + if (this.entitiesTableConfig.defaultSortOrder || routerQueryParams.hasOwnProperty('direction') || routerQueryParams.hasOwnProperty('property')) { sortOrder = { - property: this.entitiesTableConfig.defaultSortOrder.property, - direction: this.entitiesTableConfig.defaultSortOrder.direction + property: routerQueryParams?.property || this.entitiesTableConfig.defaultSortOrder.property, + direction: routerQueryParams?.direction || this.entitiesTableConfig.defaultSortOrder.direction }; } this.displayPagination = this.entitiesTableConfig.displayPagination; this.defaultPageSize = this.entitiesTableConfig.defaultPageSize; + this.pageMode = this.entitiesTableConfig.pageMode; this.pageSizeOptions = [this.defaultPageSize, this.defaultPageSize * 2, this.defaultPageSize * 3]; if (this.entitiesTableConfig.useTimePageLink) { @@ -233,6 +238,16 @@ export class EntitiesTableComponent extends PageComponent implements IEntitiesTa this.pageLink = new PageLink(10, 0, null, sortOrder); } this.pageLink.pageSize = this.displayPagination ? this.defaultPageSize : MAX_SAFE_PAGE_SIZE; + if (routerQueryParams.hasOwnProperty('page')) { + this.pageLink.page = Number(routerQueryParams.page); + } + if (routerQueryParams.hasOwnProperty('pageSize')) { + this.pageLink.pageSize = Number(routerQueryParams.pageSize); + } + if (routerQueryParams.hasOwnProperty('textSearch') && !isEmptyStr(routerQueryParams.textSearch)) { + this.textSearchMode = true; + this.pageLink.textSearch = decodeURI(routerQueryParams.textSearch); + } this.dataSource = this.entitiesTableConfig.dataSource(this.dataLoaded.bind(this)); if (this.entitiesTableConfig.onLoadAction) { this.entitiesTableConfig.onLoadAction(this.route); @@ -254,36 +269,73 @@ export class EntitiesTableComponent extends PageComponent implements IEntitiesTa debounceTime(150), distinctUntilChanged(), tap(() => { + const queryParams: PageQueryParam = { + textSearch: encodeURI(this.pageLink.textSearch) || null + }; if (this.displayPagination) { this.paginator.pageIndex = 0; + queryParams.page = null; } - this.updateData(); + this.updatedRouterParamsAndData(queryParams); }) ) .subscribe(); + this.route.queryParams.pipe(skip(1)).subscribe((params: PageQueryParam) => { + this.paginator.pageIndex = Number(params.page) || 0; + this.paginator.pageSize = Number(params.pageSize) || this.defaultPageSize; + this.sort.active = params.property || this.entitiesTableConfig.defaultSortOrder.property; + this.sort.direction = (params.direction || this.entitiesTableConfig.defaultSortOrder.direction).toLowerCase() as SortDirection; + if (params.hasOwnProperty('textSearch') && !isEmptyStr(params.textSearch)) { + this.textSearchMode = true; + this.pageLink.textSearch = decodeURI(params.textSearch); + } else { + this.textSearchMode = false; + this.pageLink.textSearch = null; + } + this.updateData(); + }); + this.updatePaginationSubscriptions(); this.viewInited = true; } private updatePaginationSubscriptions() { - if (this.sortSubscription) { - this.sortSubscription.unsubscribe(); - this.sortSubscription = null; - } if (this.updateDataSubscription) { this.updateDataSubscription.unsubscribe(); this.updateDataSubscription = null; } + let paginatorSubscription$: Observable; + const sortSubscription$: Observable = this.sort.sortChange.asObservable().pipe( + map((data) => { + const direction = data.direction.toUpperCase(); + const queryParams: PageQueryParam = { + direction: (this.entitiesTableConfig?.defaultSortOrder?.direction === direction ? null : direction) as Direction, + property: this.entitiesTableConfig?.defaultSortOrder?.property === data.active ? null : data.active + }; + if (this.displayPagination) { + queryParams.page = null; + this.paginator.pageIndex = 0; + } + return queryParams; + }) + ); if (this.displayPagination) { - this.sortSubscription = this.sort.sortChange.subscribe(() => this.paginator.pageIndex = 0); + paginatorSubscription$ = this.paginator.page.asObservable().pipe( + map((data) => { + return { + page: data.pageIndex === 0 ? null : data.pageIndex, + pageSize: data.pageSize === this.defaultPageSize ? null : data.pageSize + }; + }) + ); } - this.updateDataSubscription = ((this.displayPagination ? merge(this.sort.sortChange, this.paginator.page) - : this.sort.sortChange) as Observable) - .pipe( - tap(() => this.updateData()) - ) - .subscribe(); + this.updateDataSubscription = ((this.displayPagination ? merge(sortSubscription$, paginatorSubscription$) + : sortSubscription$) as Observable).pipe( + tap((queryParams) => { + this.updatedRouterParamsAndData(queryParams); + }) + ).subscribe(); } addEnabled() { @@ -467,10 +519,14 @@ export class EntitiesTableComponent extends PageComponent implements IEntitiesTa exitFilterMode() { this.textSearchMode = false; this.pageLink.textSearch = null; + const queryParams: PageQueryParam = { + textSearch: null + }; if (this.displayPagination) { this.paginator.pageIndex = 0; + queryParams.page = null; } - this.updateData(); + this.updatedRouterParamsAndData(queryParams); } resetSortAndFilter(update: boolean = true, preserveTimewindow: boolean = false) { @@ -485,7 +541,7 @@ export class EntitiesTableComponent extends PageComponent implements IEntitiesTa this.sort.active = sortable.id; this.sort.direction = this.entitiesTableConfig.defaultSortOrder.direction === Direction.ASC ? 'asc' : 'desc'; if (update) { - this.updateData(); + this.updatedRouterParamsAndData({}, 'preserve'); } } @@ -603,4 +659,15 @@ export class EntitiesTableComponent extends PageComponent implements IEntitiesTa return entity.id.id; } + protected updatedRouterParamsAndData(queryParams: object, queryParamsHandling: QueryParamsHandling = 'merge') { + if (this.pageMode) { + this.router.navigate([], { + relativeTo: this.route, + queryParams, + queryParamsHandling + }); + } else { + this.updateData(); + } + } } diff --git a/ui-ngx/src/app/modules/home/components/event/event-table-config.ts b/ui-ngx/src/app/modules/home/components/event/event-table-config.ts index d407cf262b..a10200060a 100644 --- a/ui-ngx/src/app/modules/home/components/event/event-table-config.ts +++ b/ui-ngx/src/app/modules/home/components/event/event-table-config.ts @@ -92,6 +92,7 @@ export class EventTableConfig extends EntityTableConfig { this.searchEnabled = false; this.addEnabled = false; this.entitiesDeleteEnabled = false; + this.pageMode = false; this.headerComponent = EventTableHeaderComponent; diff --git a/ui-ngx/src/app/modules/home/models/entity/entities-table-config.models.ts b/ui-ngx/src/app/modules/home/models/entity/entities-table-config.models.ts index 93e68e40b7..d5866cbd74 100644 --- a/ui-ngx/src/app/modules/home/models/entity/entities-table-config.models.ts +++ b/ui-ngx/src/app/modules/home/models/entity/entities-table-config.models.ts @@ -160,6 +160,7 @@ export class EntityTableConfig, P extends PageLink = P addDialogStyle = {}; defaultSortOrder: SortOrder = {property: 'createdTime', direction: Direction.DESC}; displayPagination = true; + pageMode = true; defaultPageSize = 10; columns: Array> = []; cellActionDescriptors: Array> = []; diff --git a/ui-ngx/src/app/modules/home/models/entity/entity-table-component.models.ts b/ui-ngx/src/app/modules/home/models/entity/entity-table-component.models.ts index 20b1d15b32..3c1a7428ab 100644 --- a/ui-ngx/src/app/modules/home/models/entity/entity-table-component.models.ts +++ b/ui-ngx/src/app/modules/home/models/entity/entity-table-component.models.ts @@ -50,6 +50,7 @@ export interface IEntitiesTableComponent { displayPagination: boolean; pageSizeOptions: number[]; pageLink: PageLink; + pageMode: boolean; textSearchMode: boolean; timewindow: Timewindow; dataSource: EntitiesDataSource>; diff --git a/ui-ngx/src/app/modules/home/pages/audit-log/audit-log-routing.module.ts b/ui-ngx/src/app/modules/home/pages/audit-log/audit-log-routing.module.ts index 89d304e8f1..b471f3b540 100644 --- a/ui-ngx/src/app/modules/home/pages/audit-log/audit-log-routing.module.ts +++ b/ui-ngx/src/app/modules/home/pages/audit-log/audit-log-routing.module.ts @@ -29,7 +29,8 @@ const routes: Routes = [ breadcrumb: { label: 'audit-log.audit-logs', icon: 'track_changes' - } + }, + isPage: true } } ]; diff --git a/ui-ngx/src/app/shared/models/page/page-link.ts b/ui-ngx/src/app/shared/models/page/page-link.ts index 92fe4634f5..2528b6ce99 100644 --- a/ui-ngx/src/app/shared/models/page/page-link.ts +++ b/ui-ngx/src/app/shared/models/page/page-link.ts @@ -23,6 +23,12 @@ export const MAX_SAFE_PAGE_SIZE = 2147483647; export type PageLinkSearchFunction = (entity: T, textSearch: string, searchProperty?: string) => boolean; +export interface PageQueryParam extends Partial{ + textSearch?: string; + pageSize?: number; + page?: number; +} + export function defaultPageLinkSearchFunction(searchProperty?: string): PageLinkSearchFunction { return (entity, textSearch) => defaultPageLinkSearch(entity, textSearch, searchProperty); }