From 7e66cb938cb88dbd4f4369f540c101f6d1c584fc Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Thu, 9 Apr 2020 17:48:16 +0300 Subject: [PATCH] UI: Model improvement, new entity table methods --- ui-ngx/src/app/core/core.module.ts | 7 +- .../translate/translate-default-parser.ts | 76 +++++++++++++++++++ .../entity/entities-table.component.ts | 33 +++++--- .../models/datasource/entity-datasource.ts | 10 ++- .../entity/entities-table-config.models.ts | 3 +- .../models/telemetry/telemetry.models.ts | 4 + 6 files changed, 119 insertions(+), 14 deletions(-) create mode 100644 ui-ngx/src/app/core/translate/translate-default-parser.ts diff --git a/ui-ngx/src/app/core/core.module.ts b/ui-ngx/src/app/core/core.module.ts index 877d193a9e..b053938722 100644 --- a/ui-ngx/src/app/core/core.module.ts +++ b/ui-ngx/src/app/core/core.module.ts @@ -28,7 +28,7 @@ import { MissingTranslationHandler, TranslateCompiler, TranslateLoader, - TranslateModule + TranslateModule, TranslateParser } from '@ngx-translate/core'; import { TranslateHttpLoader } from '@ngx-translate/http-loader'; import { TbMissingTranslationHandler } from './translate/missing-translate-handler'; @@ -39,6 +39,7 @@ import { FlexLayoutModule } from '@angular/flex-layout'; import { TranslateDefaultCompiler } from '@core/translate/translate-default-compiler'; import { WINDOW_PROVIDERS } from '@core/services/window.service'; import { HotkeyModule } from 'angular2-hotkeys'; +import { TranslateDefaultParser } from '@core/translate/translate-default-parser'; export function HttpLoaderFactory(http: HttpClient) { return new TranslateHttpLoader(http, './assets/locale/locale.constant-', '.json'); @@ -67,6 +68,10 @@ export function HttpLoaderFactory(http: HttpClient) { compiler: { provide: TranslateCompiler, useClass: TranslateDefaultCompiler + }, + parser: { + provide: TranslateParser, + useClass: TranslateDefaultParser } }), HotkeyModule.forRoot(), diff --git a/ui-ngx/src/app/core/translate/translate-default-parser.ts b/ui-ngx/src/app/core/translate/translate-default-parser.ts new file mode 100644 index 0000000000..1468ab195b --- /dev/null +++ b/ui-ngx/src/app/core/translate/translate-default-parser.ts @@ -0,0 +1,76 @@ +/// +/// Copyright © 2016-2020 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { Injectable } from '@angular/core'; +import { TranslateParser } from '@ngx-translate/core'; +import { isDefinedAndNotNull } from '@core/utils'; + +@Injectable({ providedIn: 'root' }) +export class TranslateDefaultParser extends TranslateParser { + templateMatcher: RegExp = /{{\s?([^{}\s]*)\s?}}/g; + + // tslint:disable-next-line:ban-types + public interpolate(expr: string | Function, params?: any): string { + let result: string; + + if (typeof expr === 'string') { + result = this.interpolateString(expr, params); + } else if (typeof expr === 'function') { + result = this.interpolateFunction(expr, params); + if (typeof result === 'string') { + result = this.interpolateString(result, params); + } + } else { + result = expr as string; + } + + return result; + } + + getValue(target: any, key: string): any { + const keys = typeof key === 'string' ? key.split('.') : [key]; + key = ''; + do { + key += keys.shift(); + if (isDefinedAndNotNull(target) && isDefinedAndNotNull(target[key]) && (typeof target[key] === 'object' || !keys.length)) { + target = target[key]; + key = ''; + } else if (!keys.length) { + target = undefined; + } else { + key += '.'; + } + } while (keys.length); + + return target; + } + + // tslint:disable-next-line:ban-types + private interpolateFunction(fn: Function, params?: any) { + return fn(params); + } + + private interpolateString(expr: string, params?: any) { + if (!params) { + return expr; + } + + return expr.replace(this.templateMatcher, (substring: string, b: string) => { + const r = this.getValue(params, b); + return isDefinedAndNotNull(r) ? r : substring; + }); + } +} 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 5cf613cfa1..ac40bc7587 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 @@ -16,7 +16,7 @@ import { AfterViewInit, - ChangeDetectionStrategy, + ChangeDetectionStrategy, ChangeDetectorRef, Component, ComponentFactoryResolver, ElementRef, EventEmitter, @@ -115,6 +115,7 @@ export class EntitiesTableComponent extends PageComponent implements AfterViewIn public dialog: MatDialog, private dialogService: DialogService, private domSanitizer: DomSanitizer, + private cd: ChangeDetectorRef, private componentFactoryResolver: ComponentFactoryResolver) { super(store); } @@ -204,9 +205,7 @@ export class EntitiesTableComponent extends PageComponent implements AfterViewIn this.pageLink = new PageLink(10, 0, null, sortOrder); } this.pageLink.pageSize = this.displayPagination ? this.defaultPageSize : MAX_SAFE_PAGE_SIZE; - this.dataSource = this.entitiesTableConfig.dataSource(() => { - this.dataLoaded(); - }); + this.dataSource = this.entitiesTableConfig.dataSource(this.dataLoaded.bind(this)); if (this.entitiesTableConfig.onLoadAction) { this.entitiesTableConfig.onLoadAction(this.route); } @@ -262,6 +261,11 @@ export class EntitiesTableComponent extends PageComponent implements AfterViewIn return this.entitiesTableConfig.addEnabled; } + clearSelection() { + this.dataSource.selection.clear(); + this.cd.detectChanges(); + } + updateData(closeDetails: boolean = true) { if (closeDetails) { this.isDetailsOpen = false; @@ -294,11 +298,15 @@ export class EntitiesTableComponent extends PageComponent implements AfterViewIn this.dataSource.loadEntities(this.pageLink); } - private dataLoaded() { - this.headerCellStyleCache.length = 0; - this.cellContentCache.length = 0; - this.cellTooltipCache.length = 0; - this.cellStyleCache.length = 0; + private dataLoaded(col?: number, row?: number) { + if (isFinite(col) && isFinite(row)) { + this.clearCellCache(col, row); + } else { + this.headerCellStyleCache.length = 0; + this.cellContentCache.length = 0; + this.cellTooltipCache.length = 0; + this.cellStyleCache.length = 0; + } } onRowClick($event: Event, entity) { @@ -485,6 +493,13 @@ export class EntitiesTableComponent extends PageComponent implements AfterViewIn return res; } + clearCellCache(col: number, row: number) { + const index = row * this.entitiesTableConfig.columns.length + col; + this.cellContentCache[index] = undefined; + this.cellTooltipCache[index] = undefined; + this.cellStyleCache[index] = undefined; + } + cellContent(entity: BaseData, column: EntityColumn>, row: number) { if (column instanceof EntityTableColumn) { const col = this.entitiesTableConfig.columns.indexOf(column); diff --git a/ui-ngx/src/app/modules/home/models/datasource/entity-datasource.ts b/ui-ngx/src/app/modules/home/models/datasource/entity-datasource.ts index ef2344354b..53ca40ebbb 100644 --- a/ui-ngx/src/app/modules/home/models/datasource/entity-datasource.ts +++ b/ui-ngx/src/app/modules/home/models/datasource/entity-datasource.ts @@ -37,7 +37,7 @@ export class EntitiesDataSource, P extends PageLink = constructor(private fetchFunction: EntitiesFetchFunction, protected selectionEnabledFunction: EntityBooleanFunction, - protected dataLoadedFunction: () => void) {} + protected dataLoadedFunction: (col?: number, row?: number) => void) {} connect(collectionViewer: CollectionViewer): Observable> { return this.entitiesSubject.asObservable(); @@ -50,7 +50,7 @@ export class EntitiesDataSource, P extends PageLink = reset() { const pageData = emptyPageData(); - this.entitiesSubject.next(pageData.data); + this.onEntities(pageData.data); this.pageDataSubject.next(pageData); this.dataLoadedFunction(); } @@ -64,7 +64,7 @@ export class EntitiesDataSource, P extends PageLink = catchError(() => of(emptyPageData())), ).subscribe( (pageData) => { - this.entitiesSubject.next(pageData.data); + this.onEntities(pageData.data); this.pageDataSubject.next(pageData); result.next(pageData); this.dataLoadedFunction(); @@ -73,6 +73,10 @@ export class EntitiesDataSource, P extends PageLink = return result; } + protected onEntities(entities: T[]) { + this.entitiesSubject.next(entities); + } + isAllSelected(): Observable { const numSelected = this.selection.selected.length; return this.entitiesSubject.pipe( 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 d8533dd98b..13b9c5eacc 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 @@ -157,7 +157,8 @@ export class EntityTableConfig, P extends PageLink = P addActionDescriptors: Array = []; headerComponent: Type>; addEntity: CreateEntityOperation = null; - dataSource: (dataLoadedFunction: () => void) => EntitiesDataSource = (dataLoadedFunction: () => void) => { + dataSource: (dataLoadedFunction: (col?: number, row?: number) => void) + => EntitiesDataSource = (dataLoadedFunction: (col?: number, row?: number) => void) => { return new EntitiesDataSource(this.entitiesFetchFunction, this.entitySelectionEnabled, dataLoadedFunction); }; detailsReadonly: EntityBooleanFunction = () => false; diff --git a/ui-ngx/src/app/shared/models/telemetry/telemetry.models.ts b/ui-ngx/src/app/shared/models/telemetry/telemetry.models.ts index d98a02e471..d75239ec68 100644 --- a/ui-ngx/src/app/shared/models/telemetry/telemetry.models.ts +++ b/ui-ngx/src/app/shared/models/telemetry/telemetry.models.ts @@ -279,6 +279,10 @@ export class TelemetrySubscriber { public unsubscribe() { this.telemetryService.unsubscribe(this); + this.complete(); + } + + public complete() { this.dataSubject.complete(); this.reconnectSubject.complete(); }