Browse Source

UI: Added filter widget type

pull/4205/head
Vladyslav_Prykhodko 6 years ago
parent
commit
a3e2106888
  1. 22
      ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-page.component.html
  2. 78
      ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-page.component.ts
  3. 42
      ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-widget-select.component.ts
  4. 23
      ui-ngx/src/app/modules/home/components/dashboard-page/widget-types-panel.component.html
  5. 36
      ui-ngx/src/app/modules/home/components/dashboard-page/widget-types-panel.component.scss
  6. 48
      ui-ngx/src/app/modules/home/components/dashboard-page/widget-types-panel.component.ts
  7. 6
      ui-ngx/src/app/modules/home/components/details-panel.component.html
  8. 7
      ui-ngx/src/app/modules/home/components/details-panel.component.ts
  9. 7
      ui-ngx/src/app/modules/home/components/home-components.module.ts
  10. 2
      ui-ngx/src/app/shared/components/widgets-bundle-search.component.html
  11. 3
      ui-ngx/src/assets/locale/locale.constant-en_US.json
  12. 3
      ui-ngx/src/styles.scss

22
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()">
<div class="prefix-title-buttons" [fxHide]="!widgetsBundle?.title" style="height: 28px; margin-right: 12px">
<button class="tb-mat-28" mat-icon-button type="button" (click)="widgetBundleSelected(null)">
<mat-icon>arrow_back</mat-icon>
</button>
</div>
<div class="header-pane" *ngIf="isAddingWidget">
<div fxLayout="row">
<div fxLayout="row" fxLayoutGap="12px">
<tb-widgets-bundle-search fxFlex
[(ngModel)]="searchBundle"
placeholder="{{ (!widgetsBundle?.title ? 'widgets-bundle.search' : 'widget.search') | translate }}"
@ -260,10 +263,21 @@
</tb-widgets-bundle-search>
</div>
</div>
<div class="details-buttons" *ngIf="isAddingWidget">
<button mat-button mat-icon-button type="button"
*ngIf="widgetTypes.length > 1"
(click)="editWidgetsTypesToDisplay($event)"
matTooltip="{{ 'widget.filter' | translate }}"
matTooltipPosition="above">
<mat-icon>filter_list</mat-icon>
</button>
</div>
<tb-dashboard-widget-select *ngIf="isAddingWidget"
[aliasController]="dashboardCtx.aliasController"
[widgetsBundle]="widgetsBundle"
[searchBundle]="searchBundle"
[filterWidgetTypes]="filterWidgetTypes"
(widgetsTypes)="updateWidgetsTypes($event)"
(widgetsBundleSelected)="widgetBundleSelected($event)"
(widgetSelected)="addWidgetFromType($event)">
</tb-dashboard-widget-select>

78
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<widgetType>) {
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();
}
}

42
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<string>('');
private widgetsTypes: Observable<Array<WidgetInfo>>;
private filterWidgetTypes$ = new BehaviorSubject<Array<widgetType>>(null);
private widgetsInfo: Observable<Array<WidgetInfo>>;
private widgetsBundleValue: WidgetsBundle;
private widgetsType = new Set<widgetType>();
widgets$: Observable<Array<WidgetInfo>>;
widgetsBundles$: Observable<Array<WidgetsBundle>>;
@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<widgetType>) {
this.filterWidgetTypes$.next(widgetTypes);
}
@Output()
widgetSelected: EventEmitter<WidgetInfo> = new EventEmitter<WidgetInfo>();
@Output()
widgetsBundleSelected: EventEmitter<WidgetsBundle> = new EventEmitter<WidgetsBundle>();
@Output()
widgetsTypes: EventEmitter<Set<widgetType>> = new EventEmitter<Set<widgetType>>();
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<Array<WidgetInfo>> {
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<Array<WidgetInfo>> {
private fetchWidget(search: string, filter: widgetType[]): Observable<Array<WidgetInfo>> {
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()) ||

23
ui-ngx/src/app/modules/home/components/dashboard-page/widget-types-panel.component.html

@ -0,0 +1,23 @@
<!--
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.
-->
<div fxLayout="column" class="mat-content mat-padding">
<mat-checkbox style="padding-bottom: 8px;" [(ngModel)]="type.display" *ngFor="let type of types"
(ngModelChange)="update()">
{{ 'widget.' + type.type | translate }}
</mat-checkbox>
</div>

36
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;
}
}

48
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<any>('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);
}
}

6
ui-ngx/src/app/modules/home/components/details-panel.component.html

@ -19,10 +19,8 @@
<mat-toolbar class="details-toolbar" color="primary" [ngStyle]="{height: headerHeightPx+'px'}">
<div class="mat-toolbar-tools" fxLayout="row" fxLayoutAlign="start center" style="height: 100%;">
<div class="mat-toolbar-tools tb-details-title-header" fxFlex fxLayout="column" fxLayoutAlign="start start">
<div class="tb-details-title" fxLayout="row" fxLayoutGap="8px" fxLayoutAlign="start center">
<button class="tb-mat-28" mat-icon-button type="button" [fxHide]="!showBackStateIcon" (click)="onBackStateDetails()">
<mat-icon>arrow_back</mat-icon>
</button>
<div class="tb-details-title" fxLayout="row" fxLayoutAlign="start center">
<ng-content select=".prefix-title-buttons"></ng-content>
<span>{{ headerTitle }}</span>
</div>
<span class="tb-details-subtitle">{{ headerSubtitle }}</span>

7
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<boolean>();
@Output()
applyDetails = new EventEmitter<void>();
@Output()
backStateDetails = new EventEmitter<void>();
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;

7
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,

2
ui-ngx/src/app/shared/components/widgets-bundle-search.component.html

@ -18,7 +18,7 @@
<div class="input-wrapper" fxLayoutAlign="start center" fxLayoutGap="8px">
<mat-icon>search</mat-icon>
<input type="text" [(ngModel)]="searchText" (ngModelChange)="updateSearchText()" [placeholder]=placeholder>
<button mat-button *ngIf="searchText" mat-icon-button (click)="clear()">
<button mat-button type="button" *ngIf="searchText" mat-icon-button (click)="clear()">
<mat-icon>close</mat-icon>
</button>
</div>

3
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",

3
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);
}

Loading…
Cancel
Save