diff --git a/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-page.component.html b/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-page.component.html index 1fdc185b6c..6e9ba975ba 100644 --- a/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-page.component.html +++ b/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-page.component.html @@ -248,11 +248,14 @@ headerHeightPx="120" [isReadOnly]="true" [isEdit]="false" - [showBackStateIcon]="!!widgetsBundle?.title" - (closeDetails)="onAddWidgetClosed()" - (backStateDetails)="widgetBundleSelected(null)"> + (closeDetails)="onAddWidgetClosed()"> +
+ +
-
+
+
+ +
diff --git a/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-page.component.ts b/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-page.component.ts index 19c93d0309..3695653230 100644 --- a/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-page.component.ts +++ b/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-page.component.ts @@ -18,11 +18,14 @@ import { ChangeDetectorRef, Component, Inject, + Injector, Input, NgZone, OnDestroy, OnInit, + StaticProvider, ViewChild, + ViewContainerRef, ViewEncapsulation } from '@angular/core'; import { PageComponent } from '@shared/components/page.component'; @@ -58,7 +61,14 @@ import { BreakpointObserver, BreakpointState } from '@angular/cdk/layout'; import { MediaBreakpoints } from '@shared/models/constants'; import { AuthUser } from '@shared/models/user.model'; import { getCurrentAuthState } from '@core/auth/auth.selectors'; -import { Widget, WidgetConfig, WidgetInfo, WidgetPosition, widgetTypesData } from '@app/shared/models/widget.models'; +import { + Widget, + WidgetConfig, + WidgetInfo, + WidgetPosition, + widgetType, + widgetTypesData +} from '@shared/models/widget.models'; import { environment as env } from '@env/environment'; import { Authority } from '@shared/models/authority.enum'; import { DialogService } from '@core/services/dialog.service'; @@ -80,7 +90,10 @@ import { import { EntityAliases } from '@app/shared/models/alias.models'; import { EditWidgetComponent } from '@home/components/dashboard-page/edit-widget.component'; import { WidgetsBundle } from '@shared/models/widgets-bundle.model'; -import { AddWidgetDialogComponent, AddWidgetDialogData } from '@home/components/dashboard-page/add-widget-dialog.component'; +import { + AddWidgetDialogComponent, + AddWidgetDialogData +} from '@home/components/dashboard-page/add-widget-dialog.component'; import { TranslateService } from '@ngx-translate/core'; import { ManageDashboardLayoutsDialogComponent, @@ -99,6 +112,14 @@ import { ImportExportService } from '@home/components/import-export/import-expor import { AuthState } from '@app/core/auth/auth.models'; import { FiltersDialogComponent, FiltersDialogData } from '@home/components/filter/filters-dialog.component'; import { Filters } from '@shared/models/query/query.models'; +import { ConnectedPosition, Overlay, OverlayConfig, OverlayRef } from '@angular/cdk/overlay'; +import { ComponentPortal } from '@angular/cdk/portal'; +import { + DISPLAY_WIDGET_TYPES_PANEL_DATA, + DisplayWidgetTypesPanelComponent, + DisplayWidgetTypesPanelData, + WidgetTypes +} from '@home/components/dashboard-page/widget-types-panel.component'; // @dynamic @Component({ @@ -147,6 +168,8 @@ export class DashboardPageComponent extends PageComponent implements IDashboardC isAddingWidgetClosed = true; widgetsBundle: WidgetsBundle = null; searchBundle = ''; + widgetTypes: WidgetTypes[] = []; + filterWidgetTypes: widgetType[] = null; isToolbarOpened = false; isToolbarOpenedAnimate = false; @@ -261,6 +284,8 @@ export class DashboardPageComponent extends PageComponent implements IDashboardC private dialog: MatDialog, private translate: TranslateService, private ngZone: NgZone, + private overlay: Overlay, + private viewContainerRef: ViewContainerRef, private cd: ChangeDetectorRef) { super(store); @@ -1121,6 +1146,55 @@ export class DashboardPageComponent extends PageComponent implements IDashboardC widgetBundleSelected(bundle: WidgetsBundle){ this.widgetsBundle = bundle; + this.widgetTypes = []; this.searchBundle = ''; } + + updateWidgetsTypes(types: Set) { + this.widgetTypes = Array.from(types.values()).map(type => { + return {type, display: true}; + }); + } + + editWidgetsTypesToDisplay($event: Event) { + if ($event) { + $event.stopPropagation(); + } + const target = $event.target || $event.currentTarget; + const config = new OverlayConfig(); + config.backdropClass = 'cdk-overlay-transparent-backdrop'; + config.hasBackdrop = true; + const connectedPosition: ConnectedPosition = { + originX: 'end', + originY: 'bottom', + overlayX: 'end', + overlayY: 'top' + }; + config.positionStrategy = this.overlay.position().flexibleConnectedTo(target as HTMLElement) + .withPositions([connectedPosition]); + + const overlayRef = this.overlay.create(config); + overlayRef.backdropClick().subscribe(() => { + overlayRef.dispose(); + }); + + const providers: StaticProvider[] = [ + { + provide: DISPLAY_WIDGET_TYPES_PANEL_DATA, + useValue: { + types: this.widgetTypes, + typesUpdated: (newTypes) => { + this.filterWidgetTypes = newTypes.filter(type => type.display).map(type => type.type); + } + } as DisplayWidgetTypesPanelData + }, + { + provide: OverlayRef, + useValue: overlayRef + } + ]; + const injector = Injector.create({parent: this.viewContainerRef.injector, providers}); + overlayRef.attach(new ComponentPortal(DisplayWidgetTypesPanelComponent, this.viewContainerRef, injector)); + this.cd.detectChanges(); + } } diff --git a/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-widget-select.component.ts b/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-widget-select.component.ts index ae0effca12..faf4c8c5f0 100644 --- a/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-widget-select.component.ts +++ b/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-widget-select.component.ts @@ -19,10 +19,10 @@ import { WidgetsBundle } from '@shared/models/widgets-bundle.model'; import { IAliasController } from '@core/api/widget-api.models'; import { NULL_UUID } from '@shared/models/id/has-uuid'; import { WidgetService } from '@core/http/widget.service'; -import { WidgetInfo } from '@shared/models/widget.models'; +import { WidgetInfo, widgetType } from '@shared/models/widget.models'; import { toWidgetInfo } from '@home/models/widget-component.models'; -import { distinctUntilChanged, map, publishReplay, refCount, switchMap } from 'rxjs/operators'; -import { BehaviorSubject, Observable, of } from 'rxjs'; +import { distinctUntilChanged, map, publishReplay, refCount, switchMap, tap } from 'rxjs/operators'; +import { BehaviorSubject, combineLatest, Observable, of } from 'rxjs'; import { DomSanitizer, SafeUrl } from '@angular/platform-browser'; import { isDefinedAndNotNull } from '@core/utils'; @@ -34,15 +34,20 @@ import { isDefinedAndNotNull } from '@core/utils'; export class DashboardWidgetSelectComponent implements OnInit { private search$ = new BehaviorSubject(''); - private widgetsTypes: Observable>; + private filterWidgetTypes$ = new BehaviorSubject>(null); + private widgetsInfo: Observable>; private widgetsBundleValue: WidgetsBundle; + private widgetsType = new Set(); widgets$: Observable>; widgetsBundles$: Observable>; @Input() set widgetsBundle(widgetBundle: WidgetsBundle) { - this.widgetsTypes = null; + this.widgetsInfo = null; + this.widgetsType.clear(); + this.widgetsTypes.emit(this.widgetsType); + this.filterWidgetTypes$.next(null); this.widgetsBundleValue = widgetBundle; } @@ -58,12 +63,20 @@ export class DashboardWidgetSelectComponent implements OnInit { this.search$.next(search); } + @Input() + set filterWidgetTypes(widgetTypes: Array) { + this.filterWidgetTypes$.next(widgetTypes); + } + @Output() widgetSelected: EventEmitter = new EventEmitter(); @Output() widgetsBundleSelected: EventEmitter = new EventEmitter(); + @Output() + widgetsTypes: EventEmitter> = new EventEmitter>(); + constructor(private widgetsService: WidgetService, private sanitizer: DomSanitizer) { } @@ -73,21 +86,22 @@ export class DashboardWidgetSelectComponent implements OnInit { distinctUntilChanged(), switchMap(search => this.fetchWidgetBundle(search)) ); - this.widgets$ = this.search$.asObservable().pipe( - distinctUntilChanged(), - switchMap(search => this.fetchWidget(search)) + this.widgets$ = combineLatest([this.search$.asObservable(), this.filterWidgetTypes$.asObservable()]).pipe( + distinctUntilChanged((oldValue, newValue) => JSON.stringify(oldValue) === JSON.stringify(newValue)), + switchMap(search => this.fetchWidget(...search)) ); } private getWidgets(): Observable> { - if (!this.widgetsTypes) { + if (!this.widgetsInfo) { if (this.widgetsBundle !== null) { const bundleAlias = this.widgetsBundle.alias; const isSystem = this.widgetsBundle.tenantId.id === NULL_UUID; - this.widgetsTypes = this.widgetsService.getBundleWidgetTypes(bundleAlias, isSystem).pipe( + this.widgetsInfo = this.widgetsService.getBundleWidgetTypes(bundleAlias, isSystem).pipe( map(widgets => widgets.sort((a, b) => b.createdTime - a.createdTime)), map(widgets => widgets.map((type) => { const widgetTypeInfo = toWidgetInfo(type); + this.widgetsType.add(widgetTypeInfo.type); const widget: WidgetInfo = { isSystemType: isSystem, bundleAlias, @@ -99,14 +113,15 @@ export class DashboardWidgetSelectComponent implements OnInit { }; return widget; })), + tap(() => this.widgetsTypes.emit(this.widgetsType)), publishReplay(1), refCount() ); } else { - this.widgetsTypes = of([]); + this.widgetsInfo = of([]); } } - return this.widgetsTypes; + return this.widgetsInfo; } onWidgetClicked($event: Event, widget: WidgetInfo): void { @@ -149,8 +164,9 @@ export class DashboardWidgetSelectComponent implements OnInit { ); } - private fetchWidget(search: string): Observable> { + private fetchWidget(search: string, filter: widgetType[]): Observable> { return this.getWidgets().pipe( + map(widgets => filter ? widgets.filter((widget) => filter.includes(widget.type)) : widgets), map(widgets => search ? widgets.filter( widget => ( widget.title?.toLowerCase().includes(search.toLowerCase()) || diff --git a/ui-ngx/src/app/modules/home/components/dashboard-page/widget-types-panel.component.html b/ui-ngx/src/app/modules/home/components/dashboard-page/widget-types-panel.component.html new file mode 100644 index 0000000000..ebadbaacde --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/dashboard-page/widget-types-panel.component.html @@ -0,0 +1,23 @@ + +
+ + {{ 'widget.' + type.type | translate }} + +
diff --git a/ui-ngx/src/app/modules/home/components/dashboard-page/widget-types-panel.component.scss b/ui-ngx/src/app/modules/home/components/dashboard-page/widget-types-panel.component.scss new file mode 100644 index 0000000000..238326a072 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/dashboard-page/widget-types-panel.component.scss @@ -0,0 +1,36 @@ +/** + * Copyright © 2016-2021 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +:host { + width: 100%; + height: 100%; + min-width: 200px; + overflow: hidden; + background: #fff; + border-radius: 4px; + box-shadow: + 0 7px 8px -4px rgba(0, 0, 0, .2), + 0 13px 19px 2px rgba(0, 0, 0, .14), + 0 5px 24px 4px rgba(0, 0, 0, .12); + + .mat-content { + overflow: hidden; + background-color: #fff; + } + + .mat-padding { + padding: 16px; + } +} diff --git a/ui-ngx/src/app/modules/home/components/dashboard-page/widget-types-panel.component.ts b/ui-ngx/src/app/modules/home/components/dashboard-page/widget-types-panel.component.ts new file mode 100644 index 0000000000..5465f552e7 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/dashboard-page/widget-types-panel.component.ts @@ -0,0 +1,48 @@ +/// +/// Copyright © 2016-2021 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { Component, Inject, InjectionToken } from '@angular/core'; +import { widgetType } from '@shared/models/widget.models'; + +export const DISPLAY_WIDGET_TYPES_PANEL_DATA = new InjectionToken('DisplayWidgetTypesPanelData'); + +export interface WidgetTypes { + type: widgetType; + display: boolean; +} + +export interface DisplayWidgetTypesPanelData { + types: WidgetTypes[]; + typesUpdated: (columns: WidgetTypes[]) => void; +} + +@Component({ + selector: 'tb-widget-types-panel', + templateUrl: './widget-types-panel.component.html', + styleUrls: ['./widget-types-panel.component.scss'] +}) +export class DisplayWidgetTypesPanelComponent { + + types: WidgetTypes[]; + + constructor(@Inject(DISPLAY_WIDGET_TYPES_PANEL_DATA) public data: DisplayWidgetTypesPanelData) { + this.types = this.data.types; + } + + public update() { + this.data.typesUpdated(this.types); + } +} diff --git a/ui-ngx/src/app/modules/home/components/details-panel.component.html b/ui-ngx/src/app/modules/home/components/details-panel.component.html index 7c874b3dea..9121e19391 100644 --- a/ui-ngx/src/app/modules/home/components/details-panel.component.html +++ b/ui-ngx/src/app/modules/home/components/details-panel.component.html @@ -19,10 +19,8 @@
-
- +
+ {{ headerTitle }}
{{ headerSubtitle }} diff --git a/ui-ngx/src/app/modules/home/components/details-panel.component.ts b/ui-ngx/src/app/modules/home/components/details-panel.component.ts index 5c524dda6e..85e76bcf8b 100644 --- a/ui-ngx/src/app/modules/home/components/details-panel.component.ts +++ b/ui-ngx/src/app/modules/home/components/details-panel.component.ts @@ -32,7 +32,6 @@ export class DetailsPanelComponent extends PageComponent { @Input() headerSubtitle = ''; @Input() isReadOnly = false; @Input() isAlwaysEdit = false; - @Input() showBackStateIcon = false; theFormValue: FormGroup; @@ -51,8 +50,6 @@ export class DetailsPanelComponent extends PageComponent { toggleDetailsEditMode = new EventEmitter(); @Output() applyDetails = new EventEmitter(); - @Output() - backStateDetails = new EventEmitter(); isEditValue = false; @@ -78,10 +75,6 @@ export class DetailsPanelComponent extends PageComponent { this.closeDetails.emit(); } - onBackStateDetails() { - this.backStateDetails.emit(); - } - onToggleDetailsEditMode() { if (!this.isAlwaysEdit) { this.isEdit = !this.isEdit; diff --git a/ui-ngx/src/app/modules/home/components/home-components.module.ts b/ui-ngx/src/app/modules/home/components/home-components.module.ts index c7863381ef..3f25e62078 100644 --- a/ui-ngx/src/app/modules/home/components/home-components.module.ts +++ b/ui-ngx/src/app/modules/home/components/home-components.module.ts @@ -132,6 +132,7 @@ import { ManageDashboardStatesDialogComponent } from '@home/components/dashboard import { DashboardStateDialogComponent } from '@home/components/dashboard-page/states/dashboard-state-dialog.component'; import { EmbedDashboardDialogComponent } from '@home/components/widget/dialog/embed-dashboard-dialog.component'; import { EMBED_DASHBOARD_DIALOG_TOKEN } from '@home/components/widget/dialog/embed-dashboard-dialog-token'; +import { DisplayWidgetTypesPanelComponent } from '@home/components/dashboard-page/widget-types-panel.component'; @NgModule({ declarations: @@ -244,7 +245,8 @@ import { EMBED_DASHBOARD_DIALOG_TOKEN } from '@home/components/widget/dialog/emb DashboardSettingsDialogComponent, ManageDashboardStatesDialogComponent, DashboardStateDialogComponent, - EmbedDashboardDialogComponent + EmbedDashboardDialogComponent, + DisplayWidgetTypesPanelComponent ], imports: [ CommonModule, @@ -345,7 +347,8 @@ import { EMBED_DASHBOARD_DIALOG_TOKEN } from '@home/components/widget/dialog/emb DashboardSettingsDialogComponent, ManageDashboardStatesDialogComponent, DashboardStateDialogComponent, - EmbedDashboardDialogComponent + EmbedDashboardDialogComponent, + DisplayWidgetTypesPanelComponent ], providers: [ WidgetComponentService, diff --git a/ui-ngx/src/app/shared/components/widgets-bundle-search.component.html b/ui-ngx/src/app/shared/components/widgets-bundle-search.component.html index 66fe4e661c..0a545536fa 100644 --- a/ui-ngx/src/app/shared/components/widgets-bundle-search.component.html +++ b/ui-ngx/src/app/shared/components/widgets-bundle-search.component.html @@ -18,7 +18,7 @@
search -
diff --git a/ui-ngx/src/assets/locale/locale.constant-en_US.json b/ui-ngx/src/assets/locale/locale.constant-en_US.json index 4c1b586395..f5d2990b5e 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -2265,7 +2265,8 @@ "no-data": "No data to display on widget", "data-overflow": "Widget displays {{count}} out of {{total}} entities", "alarm-data-overflow": "Widget displays alarms for {{allowedEntities}} (maximum allowed) entities out of {{totalEntities}} entities", - "search": "Search widget" + "search": "Search widget", + "filter": "Widget filter type" }, "widget-action": { "header-button": "Widget header button", diff --git a/ui-ngx/src/styles.scss b/ui-ngx/src/styles.scss index 5a4db51b0e..1abab9ef9f 100644 --- a/ui-ngx/src/styles.scss +++ b/ui-ngx/src/styles.scss @@ -753,6 +753,9 @@ mat-label { &.tb-mat-20 { @include tb-mat-icon-size(20); } + &.tb-mat-28 { + @include tb-mat-icon-size(28); + } &.tb-mat-32 { @include tb-mat-icon-size(32); }