diff --git a/ui-ngx/src/app/core/api/data-aggregator.ts b/ui-ngx/src/app/core/api/data-aggregator.ts index 4437538719..a57ad25d93 100644 --- a/ui-ngx/src/app/core/api/data-aggregator.ts +++ b/ui-ngx/src/app/core/api/data-aggregator.ts @@ -155,6 +155,7 @@ export class DataAggregator { } public onData(data: SubscriptionDataHolder, update: boolean, history: boolean, detectChanges: boolean) { + this.updatedData = true; if (!this.dataReceived || this.resetPending) { let updateIntervalScheduledTime = true; if (!this.dataReceived) { @@ -183,7 +184,6 @@ export class DataAggregator { this.onInterval(history, detectChanges); } } - this.updatedData = true; } private onInterval(history?: boolean, detectChanges?: boolean) { diff --git a/ui-ngx/src/app/core/http/widget.service.ts b/ui-ngx/src/app/core/http/widget.service.ts index be44d19f9b..f490f90d89 100644 --- a/ui-ngx/src/app/core/http/widget.service.ts +++ b/ui-ngx/src/app/core/http/widget.service.ts @@ -55,6 +55,8 @@ export class WidgetService { private systemWidgetsBundles: Array; private tenantWidgetsBundles: Array; + private widgetTypeInfosCache = new Map>(); + private loadWidgetsBundleCacheSubject: ReplaySubject; constructor( @@ -137,8 +139,15 @@ export class WidgetService { public getBundleWidgetTypeInfos(bundleAlias: string, isSystem: boolean, config?: RequestConfig): Observable> { - return this.http.get>(`/api/widgetTypesInfos?isSystem=${isSystem}&bundleAlias=${bundleAlias}`, - defaultHttpOptionsFromConfig(config)); + const key = bundleAlias + (isSystem ? '_sys' : ''); + if (this.widgetTypeInfosCache.has(key)) { + return of(this.widgetTypeInfosCache.get(key)); + } else { + return this.http.get>(`/api/widgetTypesInfos?isSystem=${isSystem}&bundleAlias=${bundleAlias}`, + defaultHttpOptionsFromConfig(config)).pipe( + tap((res) => this.widgetTypeInfosCache.set(key, res) ) + ); + } } public loadBundleLibraryWidgets(bundleAlias: string, isSystem: boolean, @@ -305,6 +314,7 @@ export class WidgetService { this.systemWidgetsBundles = undefined; this.tenantWidgetsBundles = undefined; this.loadWidgetsBundleCacheSubject = undefined; + this.widgetTypeInfosCache.clear(); } } diff --git a/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-page.component.html b/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-page.component.html index f82a7a82ca..1cc501a0d1 100644 --- a/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-page.component.html +++ b/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-page.component.html @@ -242,8 +242,9 @@ -
-
-
diff --git a/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-page.component.ts b/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-page.component.ts index 70ee0c269c..45ff291954 100644 --- a/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-page.component.ts +++ b/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-page.component.ts @@ -121,6 +121,7 @@ import { DisplayWidgetTypesPanelData, WidgetTypes } from '@home/components/dashboard-page/widget-types-panel.component'; +import { DashboardWidgetSelectComponent } from '@home/components/dashboard-page/dashboard-widget-select.component'; // @dynamic @Component({ @@ -167,9 +168,7 @@ export class DashboardPageComponent extends PageComponent implements IDashboardC forceDashboardMobileMode = false; isAddingWidget = false; isAddingWidgetClosed = true; - widgetsBundle: WidgetsBundle = null; searchBundle = ''; - widgetTypes: WidgetTypes[] = []; filterWidgetTypes: widgetType[] = null; isToolbarOpened = false; @@ -267,6 +266,8 @@ export class DashboardPageComponent extends PageComponent implements IDashboardC @ViewChild('tbEditWidget') editWidgetComponent: EditWidgetComponent; + @ViewChild('dashboardWidgetSelect') dashboardWidgetSelectComponent: DashboardWidgetSelectComponent; + constructor(protected store: Store, @Inject(WINDOW) private window: Window, private breakpointObserver: BreakpointObserver, @@ -367,7 +368,6 @@ export class DashboardPageComponent extends PageComponent implements IDashboardC this.forceDashboardMobileMode = false; this.isAddingWidget = false; this.isAddingWidgetClosed = true; - this.widgetsBundle = null; this.isToolbarOpened = false; this.isToolbarOpenedAnimate = false; @@ -885,7 +885,6 @@ export class DashboardPageComponent extends PageComponent implements IDashboardC addWidgetFromType(widget: WidgetInfo) { this.onAddWidgetClosed(); - this.widgetTypes = []; this.searchBundle = ''; this.widgetComponentService.getWidgetInfo(widget.bundleAlias, widget.typeAlias, widget.isSystemType).subscribe( (widgetTypeInfo) => { @@ -1153,16 +1152,13 @@ export class DashboardPageComponent extends PageComponent implements IDashboardC return widgetContextActions; } - widgetBundleSelected(bundle: WidgetsBundle){ - this.widgetsBundle = bundle; - this.widgetTypes = []; + widgetBundleSelected(){ this.searchBundle = ''; } - updateWidgetsTypes(types: Set) { - this.widgetTypes = Array.from(types.values()).map(type => { - return {type, display: true}; - }); + clearSelectedWidgetBundle() { + this.searchBundle = ''; + this.dashboardWidgetSelectComponent.widgetsBundle = null; } editWidgetsTypesToDisplay($event: Event) { @@ -1191,7 +1187,9 @@ export class DashboardPageComponent extends PageComponent implements IDashboardC { provide: DISPLAY_WIDGET_TYPES_PANEL_DATA, useValue: { - types: this.widgetTypes, + types: Array.from(this.dashboardWidgetSelectComponent.widgetTypes.values()).map(type => { + return {type, display: true}; + }), typesUpdated: (newTypes) => { this.filterWidgetTypes = newTypes.filter(type => type.display).map(type => type.type); } diff --git a/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-widget-select.component.html b/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-widget-select.component.html index 1e68d62d22..68d6c3f030 100644 --- a/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-widget-select.component.html +++ b/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-widget-select.component.html @@ -17,7 +17,7 @@ -->
-
+
@@ -33,15 +33,24 @@
- - widgets-bundle.empty + +
+ + {{ 'widget.loading-widgets' | translate }} + + +
+ + widgets-bundle.empty +
-
+
@@ -57,5 +66,20 @@
+ +
+ + {{ 'widgets-bundle.loading-widgets-bundles' | translate }} + + +
+ + widgets-bundle.no-widgets-bundles-text + +
diff --git a/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-widget-select.component.scss b/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-widget-select.component.scss index 12ac10298e..30a9dc7c2b 100644 --- a/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-widget-select.component.scss +++ b/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-widget-select.component.scss @@ -29,8 +29,15 @@ flex: 0 0 100%; max-width: 100%; + &:hover { + .mat-card { + box-shadow: 0 2px 6px 6px rgb(0 0 0 / 20%), 0 1px 4px 2px rgb(0 0 0 / 14%), 0 1px 6px 0 rgb(0 0 0 / 12%) + } + } + .mat-card { cursor: pointer; + transition: box-shadow 0.2s; .preview-container { text-align: center; diff --git a/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-widget-select.component.ts b/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-widget-select.component.ts index 5e8cc7f36f..c51cc9c37f 100644 --- a/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-widget-select.component.ts +++ b/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-widget-select.component.ts @@ -20,8 +20,7 @@ import { IAliasController } from '@core/api/widget-api.models'; import { NULL_UUID } from '@shared/models/id/has-uuid'; import { WidgetService } from '@core/http/widget.service'; import { WidgetInfo, widgetType } from '@shared/models/widget.models'; -import { toWidgetInfo } from '@home/models/widget-component.models'; -import { distinctUntilChanged, map, publishReplay, refCount, switchMap, tap } from 'rxjs/operators'; +import { distinctUntilChanged, map, publishReplay, refCount, share, switchMap, tap } from 'rxjs/operators'; import { BehaviorSubject, combineLatest, Observable, of } from 'rxjs'; import { DomSanitizer, SafeUrl } from '@angular/platform-browser'; import { isDefinedAndNotNull } from '@core/utils'; @@ -37,18 +36,28 @@ export class DashboardWidgetSelectComponent implements OnInit { private filterWidgetTypes$ = new BehaviorSubject>(null); private widgetsInfo: Observable>; private widgetsBundleValue: WidgetsBundle; - private widgetsType = new Set(); + widgetTypes = new Set(); widgets$: Observable>; + loadingWidgetsSubject: BehaviorSubject = new BehaviorSubject(false); + loadingWidgets$ = this.loadingWidgetsSubject.pipe( + share() + ); widgetsBundles$: Observable>; + loadingWidgetBundlesSubject: BehaviorSubject = new BehaviorSubject(true); + loadingWidgetBundles$ = this.loadingWidgetBundlesSubject.pipe( + share() + ); - @Input() set widgetsBundle(widgetBundle: WidgetsBundle) { - this.widgetsInfo = null; - this.widgetsType.clear(); - this.widgetsTypes.emit(this.widgetsType); - this.filterWidgetTypes$.next(null); - this.widgetsBundleValue = widgetBundle; + if (this.widgetsBundleValue !== widgetBundle) { + this.widgetsBundleValue = widgetBundle; + if (widgetBundle === null) { + this.widgetTypes.clear(); + } + this.filterWidgetTypes$.next(null); + this.widgetsInfo = null; + } } get widgetsBundle(): WidgetsBundle { @@ -74,14 +83,8 @@ export class DashboardWidgetSelectComponent implements OnInit { @Output() widgetsBundleSelected: EventEmitter = new EventEmitter(); - @Output() - widgetsTypes: EventEmitter> = new EventEmitter>(); - constructor(private widgetsService: WidgetService, private sanitizer: DomSanitizer) { - } - - ngOnInit(): void { this.widgetsBundles$ = this.search$.asObservable().pipe( distinctUntilChanged(), switchMap(search => this.fetchWidgetBundle(search)) @@ -92,27 +95,41 @@ export class DashboardWidgetSelectComponent implements OnInit { ); } + ngOnInit(): void { + } + private getWidgets(): Observable> { if (!this.widgetsInfo) { if (this.widgetsBundle !== null) { const bundleAlias = this.widgetsBundle.alias; const isSystem = this.widgetsBundle.tenantId.id === NULL_UUID; + this.loadingWidgetsSubject.next(true); this.widgetsInfo = this.widgetsService.getBundleWidgetTypeInfos(bundleAlias, isSystem).pipe( - map(widgets => widgets.sort((a, b) => b.createdTime - a.createdTime)), - map(widgets => widgets.map((widgetTypeInfo) => { - this.widgetsType.add(widgetTypeInfo.widgetType); - const widget: WidgetInfo = { - isSystemType: isSystem, - bundleAlias, - typeAlias: widgetTypeInfo.alias, - type: widgetTypeInfo.widgetType, - title: widgetTypeInfo.name, - image: widgetTypeInfo.image, - description: widgetTypeInfo.description - }; - return widget; - })), - tap(() => this.widgetsTypes.emit(this.widgetsType)), + map(widgets => { + widgets = widgets.sort((a, b) => b.createdTime - a.createdTime); + const widgetTypes = new Set(); + const widgetInfos = widgets.map((widgetTypeInfo) => { + widgetTypes.add(widgetTypeInfo.widgetType); + const widget: WidgetInfo = { + isSystemType: isSystem, + bundleAlias, + typeAlias: widgetTypeInfo.alias, + type: widgetTypeInfo.widgetType, + title: widgetTypeInfo.name, + image: widgetTypeInfo.image, + description: widgetTypeInfo.description + }; + return widget; + } + ); + setTimeout(() => { + this.widgetTypes = widgetTypes; + }); + return widgetInfos; + }), + tap(() => { + this.loadingWidgetsSubject.next(false); + }), publishReplay(1), refCount() ); @@ -147,6 +164,7 @@ export class DashboardWidgetSelectComponent implements OnInit { private getWidgetsBundle(): Observable> { return this.widgetsService.getAllWidgetsBundles().pipe( + tap(() => this.loadingWidgetBundlesSubject.next(false)), publishReplay(1), refCount() ); diff --git a/ui-ngx/src/assets/locale/locale.constant-en_US.json b/ui-ngx/src/assets/locale/locale.constant-en_US.json index f5d2990b5e..ec8c1458ef 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -2266,7 +2266,8 @@ "data-overflow": "Widget displays {{count}} out of {{total}} entities", "alarm-data-overflow": "Widget displays alarms for {{allowedEntities}} (maximum allowed) entities out of {{totalEntities}} entities", "search": "Search widget", - "filter": "Widget filter type" + "filter": "Widget filter type", + "loading-widgets": "Loading widgets..." }, "widget-action": { "header-button": "Widget header button", @@ -2318,7 +2319,8 @@ "invalid-widgets-bundle-file-error": "Unable to import widgets bundle: Invalid widgets bundle data structure.", "search": "Search widget bundles", "selected-widgets-bundles": "{ count, plural, 1 {1 widgets bundle} other {# widgets bundles} } selected", - "open-widgets-bundle": "Open widgets bundle" + "open-widgets-bundle": "Open widgets bundle", + "loading-widgets-bundles": "Loading widgets bundles..." }, "widget-config": { "data": "Data",