Commit a3e2106888e22c4fc372174e7a04a705f6fddb5a

Authored by Vladyslav_Prykhodko
1 parent 55b5b3f2

UI: Added filter widget type

@@ -248,11 +248,14 @@ @@ -248,11 +248,14 @@
248 headerHeightPx="120" 248 headerHeightPx="120"
249 [isReadOnly]="true" 249 [isReadOnly]="true"
250 [isEdit]="false" 250 [isEdit]="false"
251 - [showBackStateIcon]="!!widgetsBundle?.title"  
252 - (closeDetails)="onAddWidgetClosed()"  
253 - (backStateDetails)="widgetBundleSelected(null)"> 251 + (closeDetails)="onAddWidgetClosed()">
  252 + <div class="prefix-title-buttons" [fxHide]="!widgetsBundle?.title" style="height: 28px; margin-right: 12px">
  253 + <button class="tb-mat-28" mat-icon-button type="button" (click)="widgetBundleSelected(null)">
  254 + <mat-icon>arrow_back</mat-icon>
  255 + </button>
  256 + </div>
254 <div class="header-pane" *ngIf="isAddingWidget"> 257 <div class="header-pane" *ngIf="isAddingWidget">
255 - <div fxLayout="row"> 258 + <div fxLayout="row" fxLayoutGap="12px">
256 <tb-widgets-bundle-search fxFlex 259 <tb-widgets-bundle-search fxFlex
257 [(ngModel)]="searchBundle" 260 [(ngModel)]="searchBundle"
258 placeholder="{{ (!widgetsBundle?.title ? 'widgets-bundle.search' : 'widget.search') | translate }}" 261 placeholder="{{ (!widgetsBundle?.title ? 'widgets-bundle.search' : 'widget.search') | translate }}"
@@ -260,10 +263,21 @@ @@ -260,10 +263,21 @@
260 </tb-widgets-bundle-search> 263 </tb-widgets-bundle-search>
261 </div> 264 </div>
262 </div> 265 </div>
  266 + <div class="details-buttons" *ngIf="isAddingWidget">
  267 + <button mat-button mat-icon-button type="button"
  268 + *ngIf="widgetTypes.length > 1"
  269 + (click)="editWidgetsTypesToDisplay($event)"
  270 + matTooltip="{{ 'widget.filter' | translate }}"
  271 + matTooltipPosition="above">
  272 + <mat-icon>filter_list</mat-icon>
  273 + </button>
  274 + </div>
263 <tb-dashboard-widget-select *ngIf="isAddingWidget" 275 <tb-dashboard-widget-select *ngIf="isAddingWidget"
264 [aliasController]="dashboardCtx.aliasController" 276 [aliasController]="dashboardCtx.aliasController"
265 [widgetsBundle]="widgetsBundle" 277 [widgetsBundle]="widgetsBundle"
266 [searchBundle]="searchBundle" 278 [searchBundle]="searchBundle"
  279 + [filterWidgetTypes]="filterWidgetTypes"
  280 + (widgetsTypes)="updateWidgetsTypes($event)"
267 (widgetsBundleSelected)="widgetBundleSelected($event)" 281 (widgetsBundleSelected)="widgetBundleSelected($event)"
268 (widgetSelected)="addWidgetFromType($event)"> 282 (widgetSelected)="addWidgetFromType($event)">
269 </tb-dashboard-widget-select> 283 </tb-dashboard-widget-select>
@@ -18,11 +18,14 @@ import { @@ -18,11 +18,14 @@ import {
18 ChangeDetectorRef, 18 ChangeDetectorRef,
19 Component, 19 Component,
20 Inject, 20 Inject,
  21 + Injector,
21 Input, 22 Input,
22 NgZone, 23 NgZone,
23 OnDestroy, 24 OnDestroy,
24 OnInit, 25 OnInit,
  26 + StaticProvider,
25 ViewChild, 27 ViewChild,
  28 + ViewContainerRef,
26 ViewEncapsulation 29 ViewEncapsulation
27 } from '@angular/core'; 30 } from '@angular/core';
28 import { PageComponent } from '@shared/components/page.component'; 31 import { PageComponent } from '@shared/components/page.component';
@@ -58,7 +61,14 @@ import { BreakpointObserver, BreakpointState } from '@angular/cdk/layout'; @@ -58,7 +61,14 @@ import { BreakpointObserver, BreakpointState } from '@angular/cdk/layout';
58 import { MediaBreakpoints } from '@shared/models/constants'; 61 import { MediaBreakpoints } from '@shared/models/constants';
59 import { AuthUser } from '@shared/models/user.model'; 62 import { AuthUser } from '@shared/models/user.model';
60 import { getCurrentAuthState } from '@core/auth/auth.selectors'; 63 import { getCurrentAuthState } from '@core/auth/auth.selectors';
61 -import { Widget, WidgetConfig, WidgetInfo, WidgetPosition, widgetTypesData } from '@app/shared/models/widget.models'; 64 +import {
  65 + Widget,
  66 + WidgetConfig,
  67 + WidgetInfo,
  68 + WidgetPosition,
  69 + widgetType,
  70 + widgetTypesData
  71 +} from '@shared/models/widget.models';
62 import { environment as env } from '@env/environment'; 72 import { environment as env } from '@env/environment';
63 import { Authority } from '@shared/models/authority.enum'; 73 import { Authority } from '@shared/models/authority.enum';
64 import { DialogService } from '@core/services/dialog.service'; 74 import { DialogService } from '@core/services/dialog.service';
@@ -80,7 +90,10 @@ import { @@ -80,7 +90,10 @@ import {
80 import { EntityAliases } from '@app/shared/models/alias.models'; 90 import { EntityAliases } from '@app/shared/models/alias.models';
81 import { EditWidgetComponent } from '@home/components/dashboard-page/edit-widget.component'; 91 import { EditWidgetComponent } from '@home/components/dashboard-page/edit-widget.component';
82 import { WidgetsBundle } from '@shared/models/widgets-bundle.model'; 92 import { WidgetsBundle } from '@shared/models/widgets-bundle.model';
83 -import { AddWidgetDialogComponent, AddWidgetDialogData } from '@home/components/dashboard-page/add-widget-dialog.component'; 93 +import {
  94 + AddWidgetDialogComponent,
  95 + AddWidgetDialogData
  96 +} from '@home/components/dashboard-page/add-widget-dialog.component';
84 import { TranslateService } from '@ngx-translate/core'; 97 import { TranslateService } from '@ngx-translate/core';
85 import { 98 import {
86 ManageDashboardLayoutsDialogComponent, 99 ManageDashboardLayoutsDialogComponent,
@@ -99,6 +112,14 @@ import { ImportExportService } from '@home/components/import-export/import-expor @@ -99,6 +112,14 @@ import { ImportExportService } from '@home/components/import-export/import-expor
99 import { AuthState } from '@app/core/auth/auth.models'; 112 import { AuthState } from '@app/core/auth/auth.models';
100 import { FiltersDialogComponent, FiltersDialogData } from '@home/components/filter/filters-dialog.component'; 113 import { FiltersDialogComponent, FiltersDialogData } from '@home/components/filter/filters-dialog.component';
101 import { Filters } from '@shared/models/query/query.models'; 114 import { Filters } from '@shared/models/query/query.models';
  115 +import { ConnectedPosition, Overlay, OverlayConfig, OverlayRef } from '@angular/cdk/overlay';
  116 +import { ComponentPortal } from '@angular/cdk/portal';
  117 +import {
  118 + DISPLAY_WIDGET_TYPES_PANEL_DATA,
  119 + DisplayWidgetTypesPanelComponent,
  120 + DisplayWidgetTypesPanelData,
  121 + WidgetTypes
  122 +} from '@home/components/dashboard-page/widget-types-panel.component';
102 123
103 // @dynamic 124 // @dynamic
104 @Component({ 125 @Component({
@@ -147,6 +168,8 @@ export class DashboardPageComponent extends PageComponent implements IDashboardC @@ -147,6 +168,8 @@ export class DashboardPageComponent extends PageComponent implements IDashboardC
147 isAddingWidgetClosed = true; 168 isAddingWidgetClosed = true;
148 widgetsBundle: WidgetsBundle = null; 169 widgetsBundle: WidgetsBundle = null;
149 searchBundle = ''; 170 searchBundle = '';
  171 + widgetTypes: WidgetTypes[] = [];
  172 + filterWidgetTypes: widgetType[] = null;
150 173
151 isToolbarOpened = false; 174 isToolbarOpened = false;
152 isToolbarOpenedAnimate = false; 175 isToolbarOpenedAnimate = false;
@@ -261,6 +284,8 @@ export class DashboardPageComponent extends PageComponent implements IDashboardC @@ -261,6 +284,8 @@ export class DashboardPageComponent extends PageComponent implements IDashboardC
261 private dialog: MatDialog, 284 private dialog: MatDialog,
262 private translate: TranslateService, 285 private translate: TranslateService,
263 private ngZone: NgZone, 286 private ngZone: NgZone,
  287 + private overlay: Overlay,
  288 + private viewContainerRef: ViewContainerRef,
264 private cd: ChangeDetectorRef) { 289 private cd: ChangeDetectorRef) {
265 super(store); 290 super(store);
266 291
@@ -1121,6 +1146,55 @@ export class DashboardPageComponent extends PageComponent implements IDashboardC @@ -1121,6 +1146,55 @@ export class DashboardPageComponent extends PageComponent implements IDashboardC
1121 1146
1122 widgetBundleSelected(bundle: WidgetsBundle){ 1147 widgetBundleSelected(bundle: WidgetsBundle){
1123 this.widgetsBundle = bundle; 1148 this.widgetsBundle = bundle;
  1149 + this.widgetTypes = [];
1124 this.searchBundle = ''; 1150 this.searchBundle = '';
1125 } 1151 }
  1152 +
  1153 + updateWidgetsTypes(types: Set<widgetType>) {
  1154 + this.widgetTypes = Array.from(types.values()).map(type => {
  1155 + return {type, display: true};
  1156 + });
  1157 + }
  1158 +
  1159 + editWidgetsTypesToDisplay($event: Event) {
  1160 + if ($event) {
  1161 + $event.stopPropagation();
  1162 + }
  1163 + const target = $event.target || $event.currentTarget;
  1164 + const config = new OverlayConfig();
  1165 + config.backdropClass = 'cdk-overlay-transparent-backdrop';
  1166 + config.hasBackdrop = true;
  1167 + const connectedPosition: ConnectedPosition = {
  1168 + originX: 'end',
  1169 + originY: 'bottom',
  1170 + overlayX: 'end',
  1171 + overlayY: 'top'
  1172 + };
  1173 + config.positionStrategy = this.overlay.position().flexibleConnectedTo(target as HTMLElement)
  1174 + .withPositions([connectedPosition]);
  1175 +
  1176 + const overlayRef = this.overlay.create(config);
  1177 + overlayRef.backdropClick().subscribe(() => {
  1178 + overlayRef.dispose();
  1179 + });
  1180 +
  1181 + const providers: StaticProvider[] = [
  1182 + {
  1183 + provide: DISPLAY_WIDGET_TYPES_PANEL_DATA,
  1184 + useValue: {
  1185 + types: this.widgetTypes,
  1186 + typesUpdated: (newTypes) => {
  1187 + this.filterWidgetTypes = newTypes.filter(type => type.display).map(type => type.type);
  1188 + }
  1189 + } as DisplayWidgetTypesPanelData
  1190 + },
  1191 + {
  1192 + provide: OverlayRef,
  1193 + useValue: overlayRef
  1194 + }
  1195 + ];
  1196 + const injector = Injector.create({parent: this.viewContainerRef.injector, providers});
  1197 + overlayRef.attach(new ComponentPortal(DisplayWidgetTypesPanelComponent, this.viewContainerRef, injector));
  1198 + this.cd.detectChanges();
  1199 + }
1126 } 1200 }
@@ -19,10 +19,10 @@ import { WidgetsBundle } from '@shared/models/widgets-bundle.model'; @@ -19,10 +19,10 @@ 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 { WidgetInfo } from '@shared/models/widget.models'; 22 +import { WidgetInfo, widgetType } 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, publishReplay, refCount, switchMap } from 'rxjs/operators';  
25 -import { BehaviorSubject, Observable, of } from 'rxjs'; 24 +import { distinctUntilChanged, map, publishReplay, refCount, switchMap, tap } from 'rxjs/operators';
  25 +import { BehaviorSubject, combineLatest, 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';
28 28
@@ -34,15 +34,20 @@ import { isDefinedAndNotNull } from '@core/utils'; @@ -34,15 +34,20 @@ import { isDefinedAndNotNull } from '@core/utils';
34 export class DashboardWidgetSelectComponent implements OnInit { 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>>; 37 + private filterWidgetTypes$ = new BehaviorSubject<Array<widgetType>>(null);
  38 + private widgetsInfo: Observable<Array<WidgetInfo>>;
38 private widgetsBundleValue: WidgetsBundle; 39 private widgetsBundleValue: WidgetsBundle;
  40 + private widgetsType = new Set<widgetType>();
39 41
40 widgets$: Observable<Array<WidgetInfo>>; 42 widgets$: Observable<Array<WidgetInfo>>;
41 widgetsBundles$: Observable<Array<WidgetsBundle>>; 43 widgetsBundles$: Observable<Array<WidgetsBundle>>;
42 44
43 @Input() 45 @Input()
44 set widgetsBundle(widgetBundle: WidgetsBundle) { 46 set widgetsBundle(widgetBundle: WidgetsBundle) {
45 - this.widgetsTypes = null; 47 + this.widgetsInfo = null;
  48 + this.widgetsType.clear();
  49 + this.widgetsTypes.emit(this.widgetsType);
  50 + this.filterWidgetTypes$.next(null);
46 this.widgetsBundleValue = widgetBundle; 51 this.widgetsBundleValue = widgetBundle;
47 } 52 }
48 53
@@ -58,12 +63,20 @@ export class DashboardWidgetSelectComponent implements OnInit { @@ -58,12 +63,20 @@ export class DashboardWidgetSelectComponent implements OnInit {
58 this.search$.next(search); 63 this.search$.next(search);
59 } 64 }
60 65
  66 + @Input()
  67 + set filterWidgetTypes(widgetTypes: Array<widgetType>) {
  68 + this.filterWidgetTypes$.next(widgetTypes);
  69 + }
  70 +
61 @Output() 71 @Output()
62 widgetSelected: EventEmitter<WidgetInfo> = new EventEmitter<WidgetInfo>(); 72 widgetSelected: EventEmitter<WidgetInfo> = new EventEmitter<WidgetInfo>();
63 73
64 @Output() 74 @Output()
65 widgetsBundleSelected: EventEmitter<WidgetsBundle> = new EventEmitter<WidgetsBundle>(); 75 widgetsBundleSelected: EventEmitter<WidgetsBundle> = new EventEmitter<WidgetsBundle>();
66 76
  77 + @Output()
  78 + widgetsTypes: EventEmitter<Set<widgetType>> = new EventEmitter<Set<widgetType>>();
  79 +
67 constructor(private widgetsService: WidgetService, 80 constructor(private widgetsService: WidgetService,
68 private sanitizer: DomSanitizer) { 81 private sanitizer: DomSanitizer) {
69 } 82 }
@@ -73,21 +86,22 @@ export class DashboardWidgetSelectComponent implements OnInit { @@ -73,21 +86,22 @@ export class DashboardWidgetSelectComponent implements OnInit {
73 distinctUntilChanged(), 86 distinctUntilChanged(),
74 switchMap(search => this.fetchWidgetBundle(search)) 87 switchMap(search => this.fetchWidgetBundle(search))
75 ); 88 );
76 - this.widgets$ = this.search$.asObservable().pipe(  
77 - distinctUntilChanged(),  
78 - switchMap(search => this.fetchWidget(search)) 89 + this.widgets$ = combineLatest([this.search$.asObservable(), this.filterWidgetTypes$.asObservable()]).pipe(
  90 + distinctUntilChanged((oldValue, newValue) => JSON.stringify(oldValue) === JSON.stringify(newValue)),
  91 + switchMap(search => this.fetchWidget(...search))
79 ); 92 );
80 } 93 }
81 94
82 private getWidgets(): Observable<Array<WidgetInfo>> { 95 private getWidgets(): Observable<Array<WidgetInfo>> {
83 - if (!this.widgetsTypes) { 96 + if (!this.widgetsInfo) {
84 if (this.widgetsBundle !== null) { 97 if (this.widgetsBundle !== null) {
85 const bundleAlias = this.widgetsBundle.alias; 98 const bundleAlias = this.widgetsBundle.alias;
86 const isSystem = this.widgetsBundle.tenantId.id === NULL_UUID; 99 const isSystem = this.widgetsBundle.tenantId.id === NULL_UUID;
87 - this.widgetsTypes = this.widgetsService.getBundleWidgetTypes(bundleAlias, isSystem).pipe( 100 + this.widgetsInfo = this.widgetsService.getBundleWidgetTypes(bundleAlias, isSystem).pipe(
88 map(widgets => widgets.sort((a, b) => b.createdTime - a.createdTime)), 101 map(widgets => widgets.sort((a, b) => b.createdTime - a.createdTime)),
89 map(widgets => widgets.map((type) => { 102 map(widgets => widgets.map((type) => {
90 const widgetTypeInfo = toWidgetInfo(type); 103 const widgetTypeInfo = toWidgetInfo(type);
  104 + this.widgetsType.add(widgetTypeInfo.type);
91 const widget: WidgetInfo = { 105 const widget: WidgetInfo = {
92 isSystemType: isSystem, 106 isSystemType: isSystem,
93 bundleAlias, 107 bundleAlias,
@@ -99,14 +113,15 @@ export class DashboardWidgetSelectComponent implements OnInit { @@ -99,14 +113,15 @@ export class DashboardWidgetSelectComponent implements OnInit {
99 }; 113 };
100 return widget; 114 return widget;
101 })), 115 })),
  116 + tap(() => this.widgetsTypes.emit(this.widgetsType)),
102 publishReplay(1), 117 publishReplay(1),
103 refCount() 118 refCount()
104 ); 119 );
105 } else { 120 } else {
106 - this.widgetsTypes = of([]); 121 + this.widgetsInfo = of([]);
107 } 122 }
108 } 123 }
109 - return this.widgetsTypes; 124 + return this.widgetsInfo;
110 } 125 }
111 126
112 onWidgetClicked($event: Event, widget: WidgetInfo): void { 127 onWidgetClicked($event: Event, widget: WidgetInfo): void {
@@ -149,8 +164,9 @@ export class DashboardWidgetSelectComponent implements OnInit { @@ -149,8 +164,9 @@ export class DashboardWidgetSelectComponent implements OnInit {
149 ); 164 );
150 } 165 }
151 166
152 - private fetchWidget(search: string): Observable<Array<WidgetInfo>> { 167 + private fetchWidget(search: string, filter: widgetType[]): Observable<Array<WidgetInfo>> {
153 return this.getWidgets().pipe( 168 return this.getWidgets().pipe(
  169 + map(widgets => filter ? widgets.filter((widget) => filter.includes(widget.type)) : widgets),
154 map(widgets => search ? widgets.filter( 170 map(widgets => search ? widgets.filter(
155 widget => ( 171 widget => (
156 widget.title?.toLowerCase().includes(search.toLowerCase()) || 172 widget.title?.toLowerCase().includes(search.toLowerCase()) ||
  1 +<!--
  2 +
  3 + Copyright © 2016-2021 The Thingsboard Authors
  4 +
  5 + Licensed under the Apache License, Version 2.0 (the "License");
  6 + you may not use this file except in compliance with the License.
  7 + You may obtain a copy of the License at
  8 +
  9 + http://www.apache.org/licenses/LICENSE-2.0
  10 +
  11 + Unless required by applicable law or agreed to in writing, software
  12 + distributed under the License is distributed on an "AS IS" BASIS,
  13 + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14 + See the License for the specific language governing permissions and
  15 + limitations under the License.
  16 +
  17 +-->
  18 +<div fxLayout="column" class="mat-content mat-padding">
  19 + <mat-checkbox style="padding-bottom: 8px;" [(ngModel)]="type.display" *ngFor="let type of types"
  20 + (ngModelChange)="update()">
  21 + {{ 'widget.' + type.type | translate }}
  22 + </mat-checkbox>
  23 +</div>
  1 +/**
  2 + * Copyright © 2016-2021 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +:host {
  17 + width: 100%;
  18 + height: 100%;
  19 + min-width: 200px;
  20 + overflow: hidden;
  21 + background: #fff;
  22 + border-radius: 4px;
  23 + box-shadow:
  24 + 0 7px 8px -4px rgba(0, 0, 0, .2),
  25 + 0 13px 19px 2px rgba(0, 0, 0, .14),
  26 + 0 5px 24px 4px rgba(0, 0, 0, .12);
  27 +
  28 + .mat-content {
  29 + overflow: hidden;
  30 + background-color: #fff;
  31 + }
  32 +
  33 + .mat-padding {
  34 + padding: 16px;
  35 + }
  36 +}
  1 +///
  2 +/// Copyright © 2016-2021 The Thingsboard Authors
  3 +///
  4 +/// Licensed under the Apache License, Version 2.0 (the "License");
  5 +/// you may not use this file except in compliance with the License.
  6 +/// You may obtain a copy of the License at
  7 +///
  8 +/// http://www.apache.org/licenses/LICENSE-2.0
  9 +///
  10 +/// Unless required by applicable law or agreed to in writing, software
  11 +/// distributed under the License is distributed on an "AS IS" BASIS,
  12 +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 +/// See the License for the specific language governing permissions and
  14 +/// limitations under the License.
  15 +///
  16 +
  17 +import { Component, Inject, InjectionToken } from '@angular/core';
  18 +import { widgetType } from '@shared/models/widget.models';
  19 +
  20 +export const DISPLAY_WIDGET_TYPES_PANEL_DATA = new InjectionToken<any>('DisplayWidgetTypesPanelData');
  21 +
  22 +export interface WidgetTypes {
  23 + type: widgetType;
  24 + display: boolean;
  25 +}
  26 +
  27 +export interface DisplayWidgetTypesPanelData {
  28 + types: WidgetTypes[];
  29 + typesUpdated: (columns: WidgetTypes[]) => void;
  30 +}
  31 +
  32 +@Component({
  33 + selector: 'tb-widget-types-panel',
  34 + templateUrl: './widget-types-panel.component.html',
  35 + styleUrls: ['./widget-types-panel.component.scss']
  36 +})
  37 +export class DisplayWidgetTypesPanelComponent {
  38 +
  39 + types: WidgetTypes[];
  40 +
  41 + constructor(@Inject(DISPLAY_WIDGET_TYPES_PANEL_DATA) public data: DisplayWidgetTypesPanelData) {
  42 + this.types = this.data.types;
  43 + }
  44 +
  45 + public update() {
  46 + this.data.typesUpdated(this.types);
  47 + }
  48 +}
@@ -19,10 +19,8 @@ @@ -19,10 +19,8 @@
19 <mat-toolbar class="details-toolbar" color="primary" [ngStyle]="{height: headerHeightPx+'px'}"> 19 <mat-toolbar class="details-toolbar" color="primary" [ngStyle]="{height: headerHeightPx+'px'}">
20 <div class="mat-toolbar-tools" fxLayout="row" fxLayoutAlign="start center" style="height: 100%;"> 20 <div class="mat-toolbar-tools" fxLayout="row" fxLayoutAlign="start center" style="height: 100%;">
21 <div class="mat-toolbar-tools tb-details-title-header" fxFlex fxLayout="column" fxLayoutAlign="start start"> 21 <div class="mat-toolbar-tools tb-details-title-header" fxFlex fxLayout="column" fxLayoutAlign="start start">
22 - <div class="tb-details-title" fxLayout="row" fxLayoutGap="8px" fxLayoutAlign="start center">  
23 - <button class="tb-mat-28" mat-icon-button type="button" [fxHide]="!showBackStateIcon" (click)="onBackStateDetails()">  
24 - <mat-icon>arrow_back</mat-icon>  
25 - </button> 22 + <div class="tb-details-title" fxLayout="row" fxLayoutAlign="start center">
  23 + <ng-content select=".prefix-title-buttons"></ng-content>
26 <span>{{ headerTitle }}</span> 24 <span>{{ headerTitle }}</span>
27 </div> 25 </div>
28 <span class="tb-details-subtitle">{{ headerSubtitle }}</span> 26 <span class="tb-details-subtitle">{{ headerSubtitle }}</span>
@@ -32,7 +32,6 @@ export class DetailsPanelComponent extends PageComponent { @@ -32,7 +32,6 @@ export class DetailsPanelComponent extends PageComponent {
32 @Input() headerSubtitle = ''; 32 @Input() headerSubtitle = '';
33 @Input() isReadOnly = false; 33 @Input() isReadOnly = false;
34 @Input() isAlwaysEdit = false; 34 @Input() isAlwaysEdit = false;
35 - @Input() showBackStateIcon = false;  
36 35
37 theFormValue: FormGroup; 36 theFormValue: FormGroup;
38 37
@@ -51,8 +50,6 @@ export class DetailsPanelComponent extends PageComponent { @@ -51,8 +50,6 @@ export class DetailsPanelComponent extends PageComponent {
51 toggleDetailsEditMode = new EventEmitter<boolean>(); 50 toggleDetailsEditMode = new EventEmitter<boolean>();
52 @Output() 51 @Output()
53 applyDetails = new EventEmitter<void>(); 52 applyDetails = new EventEmitter<void>();
54 - @Output()  
55 - backStateDetails = new EventEmitter<void>();  
56 53
57 isEditValue = false; 54 isEditValue = false;
58 55
@@ -78,10 +75,6 @@ export class DetailsPanelComponent extends PageComponent { @@ -78,10 +75,6 @@ export class DetailsPanelComponent extends PageComponent {
78 this.closeDetails.emit(); 75 this.closeDetails.emit();
79 } 76 }
80 77
81 - onBackStateDetails() {  
82 - this.backStateDetails.emit();  
83 - }  
84 -  
85 onToggleDetailsEditMode() { 78 onToggleDetailsEditMode() {
86 if (!this.isAlwaysEdit) { 79 if (!this.isAlwaysEdit) {
87 this.isEdit = !this.isEdit; 80 this.isEdit = !this.isEdit;
@@ -132,6 +132,7 @@ import { ManageDashboardStatesDialogComponent } from '@home/components/dashboard @@ -132,6 +132,7 @@ import { ManageDashboardStatesDialogComponent } from '@home/components/dashboard
132 import { DashboardStateDialogComponent } from '@home/components/dashboard-page/states/dashboard-state-dialog.component'; 132 import { DashboardStateDialogComponent } from '@home/components/dashboard-page/states/dashboard-state-dialog.component';
133 import { EmbedDashboardDialogComponent } from '@home/components/widget/dialog/embed-dashboard-dialog.component'; 133 import { EmbedDashboardDialogComponent } from '@home/components/widget/dialog/embed-dashboard-dialog.component';
134 import { EMBED_DASHBOARD_DIALOG_TOKEN } from '@home/components/widget/dialog/embed-dashboard-dialog-token'; 134 import { EMBED_DASHBOARD_DIALOG_TOKEN } from '@home/components/widget/dialog/embed-dashboard-dialog-token';
  135 +import { DisplayWidgetTypesPanelComponent } from '@home/components/dashboard-page/widget-types-panel.component';
135 136
136 @NgModule({ 137 @NgModule({
137 declarations: 138 declarations:
@@ -244,7 +245,8 @@ import { EMBED_DASHBOARD_DIALOG_TOKEN } from '@home/components/widget/dialog/emb @@ -244,7 +245,8 @@ import { EMBED_DASHBOARD_DIALOG_TOKEN } from '@home/components/widget/dialog/emb
244 DashboardSettingsDialogComponent, 245 DashboardSettingsDialogComponent,
245 ManageDashboardStatesDialogComponent, 246 ManageDashboardStatesDialogComponent,
246 DashboardStateDialogComponent, 247 DashboardStateDialogComponent,
247 - EmbedDashboardDialogComponent 248 + EmbedDashboardDialogComponent,
  249 + DisplayWidgetTypesPanelComponent
248 ], 250 ],
249 imports: [ 251 imports: [
250 CommonModule, 252 CommonModule,
@@ -345,7 +347,8 @@ import { EMBED_DASHBOARD_DIALOG_TOKEN } from '@home/components/widget/dialog/emb @@ -345,7 +347,8 @@ import { EMBED_DASHBOARD_DIALOG_TOKEN } from '@home/components/widget/dialog/emb
345 DashboardSettingsDialogComponent, 347 DashboardSettingsDialogComponent,
346 ManageDashboardStatesDialogComponent, 348 ManageDashboardStatesDialogComponent,
347 DashboardStateDialogComponent, 349 DashboardStateDialogComponent,
348 - EmbedDashboardDialogComponent 350 + EmbedDashboardDialogComponent,
  351 + DisplayWidgetTypesPanelComponent
349 ], 352 ],
350 providers: [ 353 providers: [
351 WidgetComponentService, 354 WidgetComponentService,
@@ -18,7 +18,7 @@ @@ -18,7 +18,7 @@
18 <div class="input-wrapper" fxLayoutAlign="start center" fxLayoutGap="8px"> 18 <div class="input-wrapper" fxLayoutAlign="start center" fxLayoutGap="8px">
19 <mat-icon>search</mat-icon> 19 <mat-icon>search</mat-icon>
20 <input type="text" [(ngModel)]="searchText" (ngModelChange)="updateSearchText()" [placeholder]=placeholder> 20 <input type="text" [(ngModel)]="searchText" (ngModelChange)="updateSearchText()" [placeholder]=placeholder>
21 - <button mat-button *ngIf="searchText" mat-icon-button (click)="clear()"> 21 + <button mat-button type="button" *ngIf="searchText" mat-icon-button (click)="clear()">
22 <mat-icon>close</mat-icon> 22 <mat-icon>close</mat-icon>
23 </button> 23 </button>
24 </div> 24 </div>
@@ -2265,7 +2265,8 @@ @@ -2265,7 +2265,8 @@
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 + "search": "Search widget",
  2269 + "filter": "Widget filter type"
2269 }, 2270 },
2270 "widget-action": { 2271 "widget-action": {
2271 "header-button": "Widget header button", 2272 "header-button": "Widget header button",
@@ -753,6 +753,9 @@ mat-label { @@ -753,6 +753,9 @@ mat-label {
753 &.tb-mat-20 { 753 &.tb-mat-20 {
754 @include tb-mat-icon-size(20); 754 @include tb-mat-icon-size(20);
755 } 755 }
  756 + &.tb-mat-28 {
  757 + @include tb-mat-icon-size(28);
  758 + }
756 &.tb-mat-32 { 759 &.tb-mat-32 {
757 @include tb-mat-icon-size(32); 760 @include tb-mat-icon-size(32);
758 } 761 }