Showing
6 changed files
with
85 additions
and
92 deletions
... | ... | @@ -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", | ... | ... |