Commit b3b97010764ae2e79684c49f219ed62d203bd3ea

Authored by Vladyslav_Prykhodko
1 parent 6c5f840d

UI: Added search widget

... ... @@ -253,10 +253,9 @@
253 253 (backStateDetails)="widgetBundleSelected(null)">
254 254 <div class="header-pane" *ngIf="isAddingWidget">
255 255 <div fxLayout="row">
256   -<!-- <span class="tb-details-subtitle">{{ 'widgets-bundle.current' | translate }}</span>-->
257 256 <tb-widgets-bundle-search fxFlex
258 257 [(ngModel)]="searchBundle"
259   - [placeholder]="!widgetsBundle?.title ? 'Search widgets bundle' : 'Search widget'"
  258 + placeholder="{{ (!widgetsBundle?.title ? 'widgets-bundle.search' : 'widget.search') | translate }}"
260 259 (ngModelChange)="searchBundle = $event">
261 260 </tb-widgets-bundle-search>
262 261 </div>
... ...
... ... @@ -58,7 +58,7 @@ import { BreakpointObserver, BreakpointState } from '@angular/cdk/layout';
58 58 import { MediaBreakpoints } from '@shared/models/constants';
59 59 import { AuthUser } from '@shared/models/user.model';
60 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 62 import { environment as env } from '@env/environment';
63 63 import { Authority } from '@shared/models/authority.enum';
64 64 import { DialogService } from '@core/services/dialog.service';
... ... @@ -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 861 this.onAddWidgetClosed();
862 862 this.widgetComponentService.getWidgetInfo(widget.bundleAlias, widget.typeAlias, widget.isSystemType).subscribe(
863 863 (widgetTypeInfo) => {
... ... @@ -1121,5 +1121,6 @@ export class DashboardPageComponent extends PageComponent implements IDashboardC
1121 1121
1122 1122 widgetBundleSelected(bundle: WidgetsBundle){
1123 1123 this.widgetsBundle = bundle;
  1124 + this.searchBundle = '';
1124 1125 }
1125 1126 }
... ...
... ... @@ -16,9 +16,9 @@
16 16
17 17 -->
18 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 22 <mat-card fxFlexFill fxLayout="row" fxLayoutGap="16px" (click)="onWidgetClicked($event, widget)">
23 23 <div class="preview-container" fxFlex="45">
24 24 <img class="preview" [src]="getPreviewImage(widget.image)" alt="{{ widget.title }}">
... ... @@ -26,32 +26,36 @@
26 26 <div fxFlex fxLayout="column">
27 27 <mat-card-title>{{widget.title}}</mat-card-title>
28 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 30 {{ widget.description }}
31 31 </mat-card-content>
32 32 </div>
33 33 </mat-card>
34 34 </div>
35 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 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 59 </div>
56   - </div>
  60 + </ng-template>
57 61 </div>
... ...
... ... @@ -14,14 +14,14 @@
14 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 18 import { WidgetsBundle } from '@shared/models/widgets-bundle.model';
19 19 import { IAliasController } from '@core/api/widget-api.models';
20 20 import { NULL_UUID } from '@shared/models/id/has-uuid';
21 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 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 25 import { BehaviorSubject, Observable, of } from 'rxjs';
26 26 import { DomSanitizer, SafeUrl } from '@angular/platform-browser';
27 27 import { isDefinedAndNotNull } from '@core/utils';
... ... @@ -31,12 +31,24 @@ import { isDefinedAndNotNull } from '@core/utils';
31 31 templateUrl: './dashboard-widget-select.component.html',
32 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 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 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 53 @Input()
42 54 aliasController: IAliasController;
... ... @@ -47,17 +59,11 @@ export class DashboardWidgetSelectComponent implements OnInit, OnChanges {
47 59 }
48 60
49 61 @Output()
50   - widgetSelected: EventEmitter<Widget> = new EventEmitter<Widget>();
  62 + widgetSelected: EventEmitter<WidgetInfo> = new EventEmitter<WidgetInfo>();
51 63
52 64 @Output()
53 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 67 constructor(private widgetsService: WidgetService,
62 68 private sanitizer: DomSanitizer) {
63 69 }
... ... @@ -65,66 +71,45 @@ export class DashboardWidgetSelectComponent implements OnInit, OnChanges {
65 71 ngOnInit(): void {
66 72 this.widgetsBundles$ = this.search$.asObservable().pipe(
67 73 distinctUntilChanged(),
68   - mergeMap(search => this.fetchWidgetBundle(search))
  74 + switchMap(search => this.fetchWidgetBundle(search))
69 75 );
70 76 this.widgets$ = this.search$.asObservable().pipe(
71 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 90 const widgetTypeInfo = toWidgetInfo(type);
99   - const widget: Widget = {
100   - typeId: type.id,
  91 + const widget: WidgetInfo = {
101 92 isSystemType: isSystem,
102 93 bundleAlias,
103 94 typeAlias: widgetTypeInfo.alias,
104 95 type: widgetTypeInfo.type,
105 96 title: widgetTypeInfo.widgetName,
106 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 113 this.widgetSelected.emit(widget);
129 114 }
130 115
... ... @@ -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 154 map(widgets => search ? widgets.filter(
170 155 widget => (
171 156 widget.title?.toLowerCase().includes(search.toLowerCase()) ||
... ...
... ... @@ -401,21 +401,24 @@ export interface WidgetConfig {
401 401 [key: string]: any;
402 402 }
403 403
404   -export interface Widget {
405   - id?: string;
  404 +export interface Widget extends WidgetInfo{
406 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 415 isSystemType: boolean;
408 416 bundleAlias: string;
409 417 typeAlias: string;
410 418 type: widgetType;
411 419 title: string;
412   - sizeX: number;
413   - sizeY: number;
414   - row: number;
415   - col: number;
416 420 image: string;
417 421 description: string;
418   - config: WidgetConfig;
419 422 }
420 423
421 424 export interface GroupInfo {
... ...
... ... @@ -2264,7 +2264,8 @@
2264 2264 "export": "Export widget",
2265 2265 "no-data": "No data to display on widget",
2266 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 2270 "widget-action": {
2270 2271 "header-button": "Widget header button",
... ...