Browse Source

UI: Added search widgets bundle

pull/4205/head
Vladyslav_Prykhodko 5 years ago
parent
commit
6c5f840de8
  1. 16
      ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-page.component.html
  2. 1
      ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-page.component.ts
  3. 51
      ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-widget-select.component.ts
  4. 2
      ui-ngx/src/app/modules/home/components/details-panel.component.html
  5. 24
      ui-ngx/src/app/shared/components/widgets-bundle-search.component.html
  6. 38
      ui-ngx/src/app/shared/components/widgets-bundle-search.component.scss
  7. 67
      ui-ngx/src/app/shared/components/widgets-bundle-search.component.ts
  8. 7
      ui-ngx/src/app/shared/shared.module.ts
  9. 1
      ui-ngx/src/assets/locale/locale.constant-en_US.json

16
ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-page.component.html

@ -242,7 +242,9 @@
</tb-edit-widget>
</tb-details-panel>
<tb-details-panel *ngIf="!isAddingWidgetClosed && !widgetEditMode" fxFlex
headerTitle="{{'dashboard.select-widget-title' | translate}}"
headerTitle="{{
(!widgetsBundle?.title ? 'widget.select-widgets-bundle' : 'dashboard.select-widget-value') | translate: widgetsBundle
}}"
headerHeightPx="120"
[isReadOnly]="true"
[isEdit]="false"
@ -252,17 +254,17 @@
<div class="header-pane" *ngIf="isAddingWidget">
<div fxLayout="row">
<!-- <span class="tb-details-subtitle">{{ 'widgets-bundle.current' | translate }}</span>-->
<tb-widgets-bundle-select fxFlex
required
[selectFirstBundle]="false"
[(ngModel)]="widgetsBundle"
(ngModelChange)="widgetsBundle = $event">
</tb-widgets-bundle-select>
<tb-widgets-bundle-search fxFlex
[(ngModel)]="searchBundle"
[placeholder]="!widgetsBundle?.title ? 'Search widgets bundle' : 'Search widget'"
(ngModelChange)="searchBundle = $event">
</tb-widgets-bundle-search>
</div>
</div>
<tb-dashboard-widget-select *ngIf="isAddingWidget"
[aliasController]="dashboardCtx.aliasController"
[widgetsBundle]="widgetsBundle"
[searchBundle]="searchBundle"
(widgetsBundleSelected)="widgetBundleSelected($event)"
(widgetSelected)="addWidgetFromType($event)">
</tb-dashboard-widget-select>

1
ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-page.component.ts

@ -146,6 +146,7 @@ export class DashboardPageComponent extends PageComponent implements IDashboardC
isAddingWidget = false;
isAddingWidgetClosed = true;
widgetsBundle: WidgetsBundle = null;
searchBundle = '';
isToolbarOpened = false;
isToolbarOpenedAnimate = false;

51
ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-widget-select.component.ts

@ -21,8 +21,8 @@ import { NULL_UUID } from '@shared/models/id/has-uuid';
import { WidgetService } from '@core/http/widget.service';
import { Widget } from '@shared/models/widget.models';
import { toWidgetInfo } from '@home/models/widget-component.models';
import { share } from 'rxjs/operators';
import { Observable } from 'rxjs';
import { distinctUntilChanged, map, mergeMap, publishReplay, refCount, share } from 'rxjs/operators';
import { BehaviorSubject, Observable, of } from 'rxjs';
import { DomSanitizer, SafeUrl } from '@angular/platform-browser';
import { isDefinedAndNotNull } from '@core/utils';
@ -33,12 +33,19 @@ import { isDefinedAndNotNull } from '@core/utils';
})
export class DashboardWidgetSelectComponent implements OnInit, OnChanges {
private search$ = new BehaviorSubject<string>('');
@Input()
widgetsBundle: WidgetsBundle;
@Input()
aliasController: IAliasController;
@Input()
set searchBundle(search: string) {
this.search$.next(search);
}
@Output()
widgetSelected: EventEmitter<Widget> = new EventEmitter<Widget>();
@ -49,13 +56,20 @@ export class DashboardWidgetSelectComponent implements OnInit, OnChanges {
widgetsBundles$: Observable<Array<WidgetsBundle>>;
widgets$: Observable<Array<Widget>>;
constructor(private widgetsService: WidgetService,
private sanitizer: DomSanitizer) {
}
ngOnInit(): void {
this.widgetsBundles$ = this.widgetsService.getAllWidgetsBundles().pipe(
share()
this.widgetsBundles$ = this.search$.asObservable().pipe(
distinctUntilChanged(),
mergeMap(search => this.fetchWidgetBundle(search))
);
this.widgets$ = this.search$.asObservable().pipe(
distinctUntilChanged(),
mergeMap(search => this.fetchWidget(search))
);
}
@ -121,6 +135,7 @@ export class DashboardWidgetSelectComponent implements OnInit, OnChanges {
selectBundle($event: Event, bundle: WidgetsBundle) {
$event.preventDefault();
this.widgetsBundle = bundle;
this.search$.next('');
this.widgetsBundleSelected.emit(bundle);
}
@ -131,4 +146,32 @@ export class DashboardWidgetSelectComponent implements OnInit, OnChanges {
return '/assets/widget-preview-empty.svg';
}
private getWidgetsBundle(): Observable<Array<WidgetsBundle>> {
return this.widgetsService.getAllWidgetsBundles().pipe(
publishReplay(1),
refCount()
);
}
private fetchWidgetBundle(search: string): Observable<Array<WidgetsBundle>> {
return this.getWidgetsBundle().pipe(
map(bundles => search ? bundles.filter(
bundle => (
bundle.title?.toLowerCase().includes(search.toLowerCase()) ||
bundle.description?.toLowerCase().includes(search.toLowerCase())
)) : bundles
)
);
}
private fetchWidget(search: string): Observable<Array<Widget>> {
return of(this.widgets).pipe(
map(widgets => search ? widgets.filter(
widget => (
widget.title?.toLowerCase().includes(search.toLowerCase()) ||
widget.description?.toLowerCase().includes(search.toLowerCase())
)) : widgets
)
);
}
}

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

@ -31,7 +31,7 @@
</span>
</div>
<ng-content select=".details-buttons"></ng-content>
<button mat-button mat-icon-button (click)="onCloseDetails()">
<button mat-button cdkFocusInitial mat-icon-button (click)="onCloseDetails()">
<mat-icon class="material-icons">close</mat-icon>
</button>
</div>

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

@ -0,0 +1,24 @@
<!--
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 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()">
<mat-icon>close</mat-icon>
</button>
</div>

38
ui-ngx/src/app/shared/components/widgets-bundle-search.component.scss

@ -0,0 +1,38 @@
/**
* 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.
*/
.input-wrapper {
background: hsla(0, 0%, 100%, .2);
padding: 5px 0 5px 10px;
height: 40px;
input {
width: 100%;
height: 100%;
padding: 0;
font-size: 20px;
outline: none;
border: none;
background-color: transparent;
color: #fff;
&::placeholder {
color: #fff;
opacity: .8;
line-height: 26px;
}
}
}

67
ui-ngx/src/app/shared/components/widgets-bundle-search.component.ts

@ -0,0 +1,67 @@
///
/// 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, ElementRef, forwardRef, Input, ViewChild, ViewEncapsulation } from '@angular/core';
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
@Component({
selector: 'tb-widgets-bundle-search',
templateUrl: './widgets-bundle-search.component.html',
styleUrls: ['./widgets-bundle-search.component.scss'],
providers: [{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => WidgetsBundleSearchComponent),
multi: true
}],
encapsulation: ViewEncapsulation.None
})
export class WidgetsBundleSearchComponent implements ControlValueAccessor {
searchText: string;
@Input() placeholder: string;
@ViewChild('searchInput') searchInput: ElementRef<HTMLInputElement>;
private propagateChange = (v: any) => { };
constructor() {
}
registerOnChange(fn: any): void {
this.propagateChange = fn;
}
registerOnTouched(fn: any): void {
}
writeValue(value: string | null): void {
this.searchText = value;
}
updateSearchText(): void {
this.updateView();
}
private updateView() {
this.propagateChange(this.searchText);
}
clear(): void {
this.searchText = '';
this.updateView();
}
}

7
ui-ngx/src/app/shared/shared.module.ts

@ -138,6 +138,7 @@ import { QueueTypeListComponent } from '@shared/components/queue/queue-type-list
import { ContactComponent } from '@shared/components/contact.component';
import { TimezoneSelectComponent } from '@shared/components/time/timezone-select.component';
import { FileSizePipe } from '@shared/pipe/file-size.pipe';
import { WidgetsBundleSearchComponent } from '@shared/components/widgets-bundle-search.component';
@NgModule({
providers: [
@ -227,7 +228,8 @@ import { FileSizePipe } from '@shared/pipe/file-size.pipe';
JsonObjectEditDialogComponent,
HistorySelectorComponent,
EntityGatewaySelectComponent,
ContactComponent
ContactComponent,
WidgetsBundleSearchComponent
],
imports: [
CommonModule,
@ -395,7 +397,8 @@ import { FileSizePipe } from '@shared/pipe/file-size.pipe';
JsonObjectEditDialogComponent,
HistorySelectorComponent,
EntityGatewaySelectComponent,
ContactComponent
ContactComponent,
WidgetsBundleSearchComponent
]
})
export class SharedModule { }

1
ui-ngx/src/assets/locale/locale.constant-en_US.json

@ -646,6 +646,7 @@
"add-widget": "Add new widget",
"title": "Title",
"select-widget-title": "Select widget",
"select-widget-value": "{{title}}: select widget",
"select-widget-subtitle": "List of available widget types",
"delete": "Delete dashboard",
"title-required": "Title is required.",

Loading…
Cancel
Save