Commit b3b97010764ae2e79684c49f219ed62d203bd3ea

Authored by Vladyslav_Prykhodko
1 parent 6c5f840d

UI: Added search widget

@@ -253,10 +253,9 @@ @@ -253,10 +253,9 @@
253 (backStateDetails)="widgetBundleSelected(null)"> 253 (backStateDetails)="widgetBundleSelected(null)">
254 <div class="header-pane" *ngIf="isAddingWidget"> 254 <div class="header-pane" *ngIf="isAddingWidget">
255 <div fxLayout="row"> 255 <div fxLayout="row">
256 -<!-- <span class="tb-details-subtitle">{{ 'widgets-bundle.current' | translate }}</span>-->  
257 <tb-widgets-bundle-search fxFlex 256 <tb-widgets-bundle-search fxFlex
258 [(ngModel)]="searchBundle" 257 [(ngModel)]="searchBundle"
259 - [placeholder]="!widgetsBundle?.title ? 'Search widgets bundle' : 'Search widget'" 258 + placeholder="{{ (!widgetsBundle?.title ? 'widgets-bundle.search' : 'widget.search') | translate }}"
260 (ngModelChange)="searchBundle = $event"> 259 (ngModelChange)="searchBundle = $event">
261 </tb-widgets-bundle-search> 260 </tb-widgets-bundle-search>
262 </div> 261 </div>
@@ -58,7 +58,7 @@ import { BreakpointObserver, BreakpointState } from '@angular/cdk/layout'; @@ -58,7 +58,7 @@ import { BreakpointObserver, BreakpointState } from '@angular/cdk/layout';
58 import { MediaBreakpoints } from '@shared/models/constants'; 58 import { MediaBreakpoints } from '@shared/models/constants';
59 import { AuthUser } from '@shared/models/user.model'; 59 import { AuthUser } from '@shared/models/user.model';
60 import { getCurrentAuthState } from '@core/auth/auth.selectors'; 60 import { getCurrentAuthState } from '@core/auth/auth.selectors';
61 -import { Widget, WidgetConfig, WidgetPosition, widgetTypesData } from '@app/shared/models/widget.models'; 61 +import { Widget, WidgetConfig, WidgetInfo, WidgetPosition, widgetTypesData } from '@app/shared/models/widget.models';
62 import { environment as env } from '@env/environment'; 62 import { environment as env } from '@env/environment';
63 import { Authority } from '@shared/models/authority.enum'; 63 import { Authority } from '@shared/models/authority.enum';
64 import { DialogService } from '@core/services/dialog.service'; 64 import { DialogService } from '@core/services/dialog.service';
@@ -857,7 +857,7 @@ export class DashboardPageComponent extends PageComponent implements IDashboardC @@ -857,7 +857,7 @@ export class DashboardPageComponent extends PageComponent implements IDashboardC
857 } 857 }
858 } 858 }
859 859
860 - addWidgetFromType(widget: Widget) { 860 + addWidgetFromType(widget: WidgetInfo) {
861 this.onAddWidgetClosed(); 861 this.onAddWidgetClosed();
862 this.widgetComponentService.getWidgetInfo(widget.bundleAlias, widget.typeAlias, widget.isSystemType).subscribe( 862 this.widgetComponentService.getWidgetInfo(widget.bundleAlias, widget.typeAlias, widget.isSystemType).subscribe(
863 (widgetTypeInfo) => { 863 (widgetTypeInfo) => {
@@ -1121,5 +1121,6 @@ export class DashboardPageComponent extends PageComponent implements IDashboardC @@ -1121,5 +1121,6 @@ export class DashboardPageComponent extends PageComponent implements IDashboardC
1121 1121
1122 widgetBundleSelected(bundle: WidgetsBundle){ 1122 widgetBundleSelected(bundle: WidgetsBundle){
1123 this.widgetsBundle = bundle; 1123 this.widgetsBundle = bundle;
  1124 + this.searchBundle = '';
1124 } 1125 }
1125 } 1126 }
@@ -16,9 +16,9 @@ @@ -16,9 +16,9 @@
16 16
17 --> 17 -->
18 <div class="widget-select"> 18 <div class="widget-select">
19 - <div *ngIf="hasWidgetTypes()">  
20 - <div fxFlexFill fxLayoutGap="12px grid" fxLayout="row wrap">  
21 - <div *ngFor="let widget of widgets" class="mat-card-container"> 19 + <div *ngIf="widgetsBundle; else bundles">
  20 + <div *ngIf="(widgets$ | async)?.length; else emptyBundle" fxFlexFill fxLayoutGap="12px grid" fxLayout="row wrap">
  21 + <div *ngFor="let widget of widgets$ | async" class="mat-card-container">
22 <mat-card fxFlexFill fxLayout="row" fxLayoutGap="16px" (click)="onWidgetClicked($event, widget)"> 22 <mat-card fxFlexFill fxLayout="row" fxLayoutGap="16px" (click)="onWidgetClicked($event, widget)">
23 <div class="preview-container" fxFlex="45"> 23 <div class="preview-container" fxFlex="45">
24 <img class="preview" [src]="getPreviewImage(widget.image)" alt="{{ widget.title }}"> 24 <img class="preview" [src]="getPreviewImage(widget.image)" alt="{{ widget.title }}">
@@ -26,32 +26,36 @@ @@ -26,32 +26,36 @@
26 <div fxFlex fxLayout="column"> 26 <div fxFlex fxLayout="column">
27 <mat-card-title>{{widget.title}}</mat-card-title> 27 <mat-card-title>{{widget.title}}</mat-card-title>
28 <mat-card-subtitle>{{ 'widget.' + widget.type | translate }}</mat-card-subtitle> 28 <mat-card-subtitle>{{ 'widget.' + widget.type | translate }}</mat-card-subtitle>
29 - <mat-card-content *ngIf="widgetsBundle.description"> 29 + <mat-card-content *ngIf="widget.description">
30 {{ widget.description }} 30 {{ widget.description }}
31 </mat-card-content> 31 </mat-card-content>
32 </div> 32 </div>
33 </mat-card> 33 </mat-card>
34 </div> 34 </div>
35 </div> 35 </div>
  36 + <ng-template #emptyBundle>
  37 + <span translate
  38 + style="display: flex;"
  39 + fxLayoutAlign="center center"
  40 + class="mat-headline tb-absolute-fill">widgets-bundle.empty</span>
  41 + </ng-template>
36 </div> 42 </div>
37 - <span translate *ngIf="widgetsBundle && !hasWidgetTypes()"  
38 - style="display: flex;"  
39 - fxLayoutAlign="center center"  
40 - class="mat-headline">widgets-bundle.empty</span>  
41 - <div *ngIf="!widgetsBundle" fxFlexFill fxLayoutGap="12px grid" fxLayout="row wrap">  
42 - <div *ngFor="let widgetsBundle of widgetsBundles$ | async" class="mat-card-container">  
43 - <mat-card fxFlexFill fxLayout="row" fxLayoutGap="16px" (click)="selectBundle($event, widgetsBundle)">  
44 - <div class="preview-container" fxFlex="45">  
45 - <img class="preview" [src]=getPreviewImage(widgetsBundle.image) alt="{{ widgetsBundle.title }}">  
46 - </div>  
47 - <div fxFlex fxLayout="column">  
48 - <mat-card-title>{{ widgetsBundle.title }}</mat-card-title>  
49 - <mat-card-subtitle *ngIf="isSystem(widgetsBundle)" translate>widgets-bundle.system</mat-card-subtitle>  
50 - <mat-card-content *ngIf="widgetsBundle.description">  
51 - {{ widgetsBundle.description }}  
52 - </mat-card-content>  
53 - </div>  
54 - </mat-card> 43 + <ng-template #bundles>
  44 + <div fxFlexFill fxLayoutGap="12px grid" fxLayout="row wrap">
  45 + <div *ngFor="let widgetsBundle of widgetsBundles$ | async" class="mat-card-container">
  46 + <mat-card fxFlexFill fxLayout="row" fxLayoutGap="16px" (click)="selectBundle($event, widgetsBundle)">
  47 + <div class="preview-container" fxFlex="45">
  48 + <img class="preview" [src]=getPreviewImage(widgetsBundle.image) alt="{{ widgetsBundle.title }}">
  49 + </div>
  50 + <div fxFlex fxLayout="column">
  51 + <mat-card-title>{{ widgetsBundle.title }}</mat-card-title>
  52 + <mat-card-subtitle *ngIf="isSystem(widgetsBundle)" translate>widgets-bundle.system</mat-card-subtitle>
  53 + <mat-card-content *ngIf="widgetsBundle.description">
  54 + {{ widgetsBundle.description }}
  55 + </mat-card-content>
  56 + </div>
  57 + </mat-card>
  58 + </div>
55 </div> 59 </div>
56 - </div> 60 + </ng-template>
57 </div> 61 </div>
@@ -14,14 +14,14 @@ @@ -14,14 +14,14 @@
14 /// limitations under the License. 14 /// limitations under the License.
15 /// 15 ///
16 16
17 -import { Component, EventEmitter, Input, OnChanges, OnInit, Output, SimpleChanges } from '@angular/core'; 17 +import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
18 import { WidgetsBundle } from '@shared/models/widgets-bundle.model'; 18 import { WidgetsBundle } from '@shared/models/widgets-bundle.model';
19 import { IAliasController } from '@core/api/widget-api.models'; 19 import { IAliasController } from '@core/api/widget-api.models';
20 import { NULL_UUID } from '@shared/models/id/has-uuid'; 20 import { NULL_UUID } from '@shared/models/id/has-uuid';
21 import { WidgetService } from '@core/http/widget.service'; 21 import { WidgetService } from '@core/http/widget.service';
22 -import { Widget } from '@shared/models/widget.models'; 22 +import { WidgetInfo } from '@shared/models/widget.models';
23 import { toWidgetInfo } from '@home/models/widget-component.models'; 23 import { toWidgetInfo } from '@home/models/widget-component.models';
24 -import { distinctUntilChanged, map, mergeMap, publishReplay, refCount, share } from 'rxjs/operators'; 24 +import { distinctUntilChanged, map, publishReplay, refCount, switchMap } from 'rxjs/operators';
25 import { BehaviorSubject, Observable, of } from 'rxjs'; 25 import { BehaviorSubject, Observable, of } from 'rxjs';
26 import { DomSanitizer, SafeUrl } from '@angular/platform-browser'; 26 import { DomSanitizer, SafeUrl } from '@angular/platform-browser';
27 import { isDefinedAndNotNull } from '@core/utils'; 27 import { isDefinedAndNotNull } from '@core/utils';
@@ -31,12 +31,24 @@ import { isDefinedAndNotNull } from '@core/utils'; @@ -31,12 +31,24 @@ import { isDefinedAndNotNull } from '@core/utils';
31 templateUrl: './dashboard-widget-select.component.html', 31 templateUrl: './dashboard-widget-select.component.html',
32 styleUrls: ['./dashboard-widget-select.component.scss'] 32 styleUrls: ['./dashboard-widget-select.component.scss']
33 }) 33 })
34 -export class DashboardWidgetSelectComponent implements OnInit, OnChanges { 34 +export class DashboardWidgetSelectComponent implements OnInit {
35 35
36 private search$ = new BehaviorSubject<string>(''); 36 private search$ = new BehaviorSubject<string>('');
  37 + private widgetsTypes: Observable<Array<WidgetInfo>>;
  38 + private widgetsBundleValue: WidgetsBundle;
  39 +
  40 + widgets$: Observable<Array<WidgetInfo>>;
  41 + widgetsBundles$: Observable<Array<WidgetsBundle>>;
37 42
38 @Input() 43 @Input()
39 - widgetsBundle: WidgetsBundle; 44 + set widgetsBundle(widgetBundle: WidgetsBundle) {
  45 + this.widgetsTypes = null;
  46 + this.widgetsBundleValue = widgetBundle;
  47 + }
  48 +
  49 + get widgetsBundle(): WidgetsBundle {
  50 + return this.widgetsBundleValue;
  51 + }
40 52
41 @Input() 53 @Input()
42 aliasController: IAliasController; 54 aliasController: IAliasController;
@@ -47,17 +59,11 @@ export class DashboardWidgetSelectComponent implements OnInit, OnChanges { @@ -47,17 +59,11 @@ export class DashboardWidgetSelectComponent implements OnInit, OnChanges {
47 } 59 }
48 60
49 @Output() 61 @Output()
50 - widgetSelected: EventEmitter<Widget> = new EventEmitter<Widget>(); 62 + widgetSelected: EventEmitter<WidgetInfo> = new EventEmitter<WidgetInfo>();
51 63
52 @Output() 64 @Output()
53 widgetsBundleSelected: EventEmitter<WidgetsBundle> = new EventEmitter<WidgetsBundle>(); 65 widgetsBundleSelected: EventEmitter<WidgetsBundle> = new EventEmitter<WidgetsBundle>();
54 66
55 - widgets: Array<Widget> = [];  
56 -  
57 - widgetsBundles$: Observable<Array<WidgetsBundle>>;  
58 -  
59 - widgets$: Observable<Array<Widget>>;  
60 -  
61 constructor(private widgetsService: WidgetService, 67 constructor(private widgetsService: WidgetService,
62 private sanitizer: DomSanitizer) { 68 private sanitizer: DomSanitizer) {
63 } 69 }
@@ -65,66 +71,45 @@ export class DashboardWidgetSelectComponent implements OnInit, OnChanges { @@ -65,66 +71,45 @@ export class DashboardWidgetSelectComponent implements OnInit, OnChanges {
65 ngOnInit(): void { 71 ngOnInit(): void {
66 this.widgetsBundles$ = this.search$.asObservable().pipe( 72 this.widgetsBundles$ = this.search$.asObservable().pipe(
67 distinctUntilChanged(), 73 distinctUntilChanged(),
68 - mergeMap(search => this.fetchWidgetBundle(search)) 74 + switchMap(search => this.fetchWidgetBundle(search))
69 ); 75 );
70 this.widgets$ = this.search$.asObservable().pipe( 76 this.widgets$ = this.search$.asObservable().pipe(
71 distinctUntilChanged(), 77 distinctUntilChanged(),
72 - mergeMap(search => this.fetchWidget(search)) 78 + switchMap(search => this.fetchWidget(search))
73 ); 79 );
74 } 80 }
75 81
76 - ngOnChanges(changes: SimpleChanges): void {  
77 - for (const propName of Object.keys(changes)) {  
78 - const change = changes[propName];  
79 - if (change.currentValue !== change.previousValue && (change.currentValue || change.currentValue === null)) {  
80 - if (propName === 'widgetsBundle') {  
81 - this.loadLibrary();  
82 - }  
83 - }  
84 - }  
85 - }  
86 -  
87 - private loadLibrary() {  
88 - this.widgets.length = 0;  
89 - if (this.widgetsBundle !== null) {  
90 - const bundleAlias = this.widgetsBundle.alias;  
91 - const isSystem = this.widgetsBundle.tenantId.id === NULL_UUID;  
92 - this.widgetsService.getBundleWidgetTypes(bundleAlias,  
93 - isSystem).subscribe(  
94 - (types) => {  
95 - types = types.sort((a, b) => b.createdTime - a.createdTime);  
96 - let top = 0;  
97 - types.forEach((type) => { 82 + private getWidgets(): Observable<Array<WidgetInfo>> {
  83 + if (!this.widgetsTypes) {
  84 + if (this.widgetsBundle !== null) {
  85 + const bundleAlias = this.widgetsBundle.alias;
  86 + const isSystem = this.widgetsBundle.tenantId.id === NULL_UUID;
  87 + this.widgetsTypes = this.widgetsService.getBundleWidgetTypes(bundleAlias, isSystem).pipe(
  88 + map(widgets => widgets.sort((a, b) => b.createdTime - a.createdTime)),
  89 + map(widgets => widgets.map((type) => {
98 const widgetTypeInfo = toWidgetInfo(type); 90 const widgetTypeInfo = toWidgetInfo(type);
99 - const widget: Widget = {  
100 - typeId: type.id, 91 + const widget: WidgetInfo = {
101 isSystemType: isSystem, 92 isSystemType: isSystem,
102 bundleAlias, 93 bundleAlias,
103 typeAlias: widgetTypeInfo.alias, 94 typeAlias: widgetTypeInfo.alias,
104 type: widgetTypeInfo.type, 95 type: widgetTypeInfo.type,
105 title: widgetTypeInfo.widgetName, 96 title: widgetTypeInfo.widgetName,
106 image: widgetTypeInfo.image, 97 image: widgetTypeInfo.image,
107 - description: widgetTypeInfo.description,  
108 - sizeX: widgetTypeInfo.sizeX,  
109 - sizeY: widgetTypeInfo.sizeY,  
110 - row: top,  
111 - col: 0,  
112 - config: JSON.parse(widgetTypeInfo.defaultConfig) 98 + description: widgetTypeInfo.description
113 }; 99 };
114 - widget.config.title = widgetTypeInfo.widgetName;  
115 - this.widgets.push(widget);  
116 - top += widget.sizeY;  
117 - });  
118 - }  
119 - ); 100 + return widget;
  101 + })),
  102 + publishReplay(1),
  103 + refCount()
  104 + );
  105 + } else {
  106 + this.widgetsTypes = of([]);
  107 + }
120 } 108 }
  109 + return this.widgetsTypes;
121 } 110 }
122 111
123 - hasWidgetTypes(): boolean {  
124 - return this.widgets.length > 0;  
125 - }  
126 -  
127 - onWidgetClicked($event: Event, widget: Widget): void { 112 + onWidgetClicked($event: Event, widget: WidgetInfo): void {
128 this.widgetSelected.emit(widget); 113 this.widgetSelected.emit(widget);
129 } 114 }
130 115
@@ -164,8 +149,8 @@ export class DashboardWidgetSelectComponent implements OnInit, OnChanges { @@ -164,8 +149,8 @@ export class DashboardWidgetSelectComponent implements OnInit, OnChanges {
164 ); 149 );
165 } 150 }
166 151
167 - private fetchWidget(search: string): Observable<Array<Widget>> {  
168 - return of(this.widgets).pipe( 152 + private fetchWidget(search: string): Observable<Array<WidgetInfo>> {
  153 + return this.getWidgets().pipe(
169 map(widgets => search ? widgets.filter( 154 map(widgets => search ? widgets.filter(
170 widget => ( 155 widget => (
171 widget.title?.toLowerCase().includes(search.toLowerCase()) || 156 widget.title?.toLowerCase().includes(search.toLowerCase()) ||
@@ -401,21 +401,24 @@ export interface WidgetConfig { @@ -401,21 +401,24 @@ export interface WidgetConfig {
401 [key: string]: any; 401 [key: string]: any;
402 } 402 }
403 403
404 -export interface Widget {  
405 - id?: string; 404 +export interface Widget extends WidgetInfo{
406 typeId?: WidgetTypeId; 405 typeId?: WidgetTypeId;
  406 + sizeX: number;
  407 + sizeY: number;
  408 + row: number;
  409 + col: number;
  410 + config: WidgetConfig;
  411 +}
  412 +
  413 +export interface WidgetInfo {
  414 + id?: string;
407 isSystemType: boolean; 415 isSystemType: boolean;
408 bundleAlias: string; 416 bundleAlias: string;
409 typeAlias: string; 417 typeAlias: string;
410 type: widgetType; 418 type: widgetType;
411 title: string; 419 title: string;
412 - sizeX: number;  
413 - sizeY: number;  
414 - row: number;  
415 - col: number;  
416 image: string; 420 image: string;
417 description: string; 421 description: string;
418 - config: WidgetConfig;  
419 } 422 }
420 423
421 export interface GroupInfo { 424 export interface GroupInfo {
@@ -2264,7 +2264,8 @@ @@ -2264,7 +2264,8 @@
2264 "export": "Export widget", 2264 "export": "Export widget",
2265 "no-data": "No data to display on widget", 2265 "no-data": "No data to display on widget",
2266 "data-overflow": "Widget displays {{count}} out of {{total}} entities", 2266 "data-overflow": "Widget displays {{count}} out of {{total}} entities",
2267 - "alarm-data-overflow": "Widget displays alarms for {{allowedEntities}} (maximum allowed) entities out of {{totalEntities}} entities" 2267 + "alarm-data-overflow": "Widget displays alarms for {{allowedEntities}} (maximum allowed) entities out of {{totalEntities}} entities",
  2268 + "search": "Search widget"
2268 }, 2269 },
2269 "widget-action": { 2270 "widget-action": {
2270 "header-button": "Widget header button", 2271 "header-button": "Widget header button",