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 5f81a97304..7929876f50 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 @@ -242,7 +242,9 @@
- - + +
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 c5f736d0a9..6332a00d7b 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 @@ -146,6 +146,7 @@ export class DashboardPageComponent extends PageComponent implements IDashboardC isAddingWidget = false; isAddingWidgetClosed = true; widgetsBundle: WidgetsBundle = null; + searchBundle = ''; isToolbarOpened = false; isToolbarOpenedAnimate = false; 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 efff3f8401..0a92a375a5 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 @@ -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(''); + @Input() widgetsBundle: WidgetsBundle; @Input() aliasController: IAliasController; + @Input() + set searchBundle(search: string) { + this.search$.next(search); + } + @Output() widgetSelected: EventEmitter = new EventEmitter(); @@ -49,13 +56,20 @@ export class DashboardWidgetSelectComponent implements OnInit, OnChanges { widgetsBundles$: Observable>; + widgets$: Observable>; + 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> { + return this.widgetsService.getAllWidgetsBundles().pipe( + publishReplay(1), + refCount() + ); + } + + private fetchWidgetBundle(search: string): Observable> { + 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> { + return of(this.widgets).pipe( + map(widgets => search ? widgets.filter( + widget => ( + widget.title?.toLowerCase().includes(search.toLowerCase()) || + widget.description?.toLowerCase().includes(search.toLowerCase()) + )) : widgets + ) + ); + } } 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 5df90d6da9..7c874b3dea 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 @@ -31,7 +31,7 @@ - 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 new file mode 100644 index 0000000000..66fe4e661c --- /dev/null +++ b/ui-ngx/src/app/shared/components/widgets-bundle-search.component.html @@ -0,0 +1,24 @@ + +
+ search + + +
diff --git a/ui-ngx/src/app/shared/components/widgets-bundle-search.component.scss b/ui-ngx/src/app/shared/components/widgets-bundle-search.component.scss new file mode 100644 index 0000000000..a434064ff7 --- /dev/null +++ b/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; + } + } +} + diff --git a/ui-ngx/src/app/shared/components/widgets-bundle-search.component.ts b/ui-ngx/src/app/shared/components/widgets-bundle-search.component.ts new file mode 100644 index 0000000000..53d2efac34 --- /dev/null +++ b/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; + + 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(); + } +} diff --git a/ui-ngx/src/app/shared/shared.module.ts b/ui-ngx/src/app/shared/shared.module.ts index b1bc1b24ab..19daca2301 100644 --- a/ui-ngx/src/app/shared/shared.module.ts +++ b/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 { } 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 405229514c..7ac7697aa4 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/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.",