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