55 changed files with 7450 additions and 2666 deletions
File diff suppressed because it is too large
@ -1,20 +0,0 @@ |
|||||
<!-- |
|
||||
|
|
||||
Copyright © 2016-2023 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. |
|
||||
|
|
||||
--> |
|
||||
<mat-form-field appearance="outline" class="tb-inline-field" subscriptSizing="dynamic"> |
|
||||
<input matInput [formControl]="unitsFormControl" placeholder="{{ 'widget-config.set' | translate }}"> |
|
||||
</mat-form-field> |
|
||||
@ -1,68 +0,0 @@ |
|||||
///
|
|
||||
/// Copyright © 2016-2023 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, forwardRef, Input, OnInit } from '@angular/core'; |
|
||||
import { ControlValueAccessor, FormControl, NG_VALUE_ACCESSOR, UntypedFormBuilder } from '@angular/forms'; |
|
||||
|
|
||||
@Component({ |
|
||||
selector: 'tb-widget-units', |
|
||||
templateUrl: './widget-units.component.html', |
|
||||
styleUrls: [], |
|
||||
providers: [ |
|
||||
{ |
|
||||
provide: NG_VALUE_ACCESSOR, |
|
||||
useExisting: forwardRef(() => WidgetUnitsComponent), |
|
||||
multi: true |
|
||||
} |
|
||||
] |
|
||||
}) |
|
||||
export class WidgetUnitsComponent implements ControlValueAccessor, OnInit { |
|
||||
|
|
||||
@Input() |
|
||||
disabled: boolean; |
|
||||
|
|
||||
unitsFormControl: FormControl; |
|
||||
|
|
||||
private propagateChange = (_val: any) => {}; |
|
||||
|
|
||||
constructor(private fb: UntypedFormBuilder) { |
|
||||
} |
|
||||
|
|
||||
ngOnInit() { |
|
||||
this.unitsFormControl = this.fb.control('', []); |
|
||||
this.unitsFormControl.valueChanges.subscribe(val => this.propagateChange(val)); |
|
||||
} |
|
||||
|
|
||||
writeValue(units?: string): void { |
|
||||
this.unitsFormControl.patchValue(units, {emitEvent: false}); |
|
||||
} |
|
||||
|
|
||||
registerOnChange(fn: any): void { |
|
||||
this.propagateChange = fn; |
|
||||
} |
|
||||
|
|
||||
registerOnTouched(fn: any): void { |
|
||||
} |
|
||||
|
|
||||
setDisabledState(isDisabled: boolean): void { |
|
||||
this.disabled = isDisabled; |
|
||||
if (this.disabled) { |
|
||||
this.unitsFormControl.disable({emitEvent: false}); |
|
||||
} else { |
|
||||
this.unitsFormControl.enable({emitEvent: false}); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -0,0 +1,65 @@ |
|||||
|
<!-- |
||||
|
|
||||
|
Copyright © 2016-2023 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="tb-material-icons-panel"> |
||||
|
<div class="tb-material-icons-title" translate>icon.icons</div> |
||||
|
<mat-form-field class="tb-material-icons-search tb-inline-field" appearance="outline" subscriptSizing="dynamic"> |
||||
|
<mat-icon matPrefix>search</mat-icon> |
||||
|
<input matInput [formControl]="searchIconControl" placeholder="{{ 'icon.search-icon' | translate }}"/> |
||||
|
<button *ngIf="searchIconControl.value" |
||||
|
type="button" |
||||
|
matSuffix mat-icon-button aria-label="Clear" |
||||
|
(click)="clearSearch()"> |
||||
|
<mat-icon class="material-icons">close</mat-icon> |
||||
|
</button> |
||||
|
</mat-form-field> |
||||
|
<cdk-virtual-scroll-viewport [fxShow]="!notFound" #iconsPanel |
||||
|
[itemSize]="iconsRowHeight" class="tb-material-icons-viewport" [ngStyle]="{width: iconsPanelWidth, height: iconsPanelHeight}"> |
||||
|
<div *cdkVirtualFor="let iconRow of iconRows$ | async" class="tb-material-icons-row"> |
||||
|
<ng-container *ngFor="let icon of iconRow"> |
||||
|
<button *ngIf="icon.name === selectedIcon" |
||||
|
class="tb-select-icon-button" |
||||
|
mat-raised-button |
||||
|
color="primary" |
||||
|
(click)="selectIcon(icon)" |
||||
|
matTooltip="{{ icon.displayName }}" |
||||
|
matTooltipPosition="above" |
||||
|
type="button"> |
||||
|
<mat-icon>{{icon.name}}</mat-icon> |
||||
|
</button> |
||||
|
<button *ngIf="icon.name !== selectedIcon" |
||||
|
class="tb-select-icon-button" |
||||
|
mat-button |
||||
|
(click)="selectIcon(icon)" |
||||
|
matTooltip="{{ icon.displayName }}" |
||||
|
matTooltipPosition="above" |
||||
|
type="button"> |
||||
|
<mat-icon>{{icon.name}}</mat-icon> |
||||
|
</button> |
||||
|
</ng-container> |
||||
|
</div> |
||||
|
</cdk-virtual-scroll-viewport> |
||||
|
<button *ngIf="!showAllSubject.value" class="tb-material-icons-show-more" mat-button color="primary" (click)="showAllSubject.next(true)"> |
||||
|
{{ 'action.show-more' | translate }} |
||||
|
</button> |
||||
|
<ng-container *ngIf="notFound"> |
||||
|
<div class="tb-no-data-available" [ngStyle]="{width: iconsPanelWidth}"> |
||||
|
<div class="tb-no-data-bg"></div> |
||||
|
<div class="tb-no-data-text">{{ 'icon.no-icons-found' | translate:{iconSearch: searchIconControl.value} }}</div> |
||||
|
</div> |
||||
|
</ng-container> |
||||
|
</div> |
||||
@ -0,0 +1,61 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2023 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. |
||||
|
*/ |
||||
|
.tb-material-icons-panel { |
||||
|
width: 100%; |
||||
|
display: flex; |
||||
|
flex-direction: column; |
||||
|
gap: 16px; |
||||
|
align-items: center; |
||||
|
.tb-material-icons-title { |
||||
|
font-size: 16px; |
||||
|
font-weight: 500; |
||||
|
line-height: 24px; |
||||
|
letter-spacing: 0.25px; |
||||
|
color: rgba(0, 0, 0, 0.87); |
||||
|
} |
||||
|
.tb-material-icons-title, .tb-material-icons-search, .tb-material-icons-show-more { |
||||
|
width: 100%; |
||||
|
} |
||||
|
.tb-material-icons-viewport { |
||||
|
min-height: 144px; |
||||
|
} |
||||
|
.tb-material-icons-row { |
||||
|
display: flex; |
||||
|
flex-direction: row; |
||||
|
gap: 12px; |
||||
|
} |
||||
|
.tb-material-icons-row + .tb-material-icons-row { |
||||
|
margin-top: 12px; |
||||
|
} |
||||
|
.tb-no-data-available { |
||||
|
min-height: 144px; |
||||
|
} |
||||
|
button.mat-mdc-button-base.tb-select-icon-button { |
||||
|
width: 36px; |
||||
|
min-width: 36px; |
||||
|
height: 36px; |
||||
|
padding: 6px; |
||||
|
&:not(.mat-primary) { |
||||
|
color: rgba(0, 0, 0, 0.54); |
||||
|
} |
||||
|
> .mat-icon { |
||||
|
width: 24px; |
||||
|
height: 24px; |
||||
|
font-size: 24px; |
||||
|
margin: 0; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,135 @@ |
|||||
|
///
|
||||
|
/// Copyright © 2016-2023 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 { PageComponent } from '@shared/components/page.component'; |
||||
|
import { |
||||
|
ChangeDetectorRef, |
||||
|
Component, |
||||
|
EventEmitter, |
||||
|
Input, |
||||
|
OnInit, |
||||
|
Output, |
||||
|
ViewChild, |
||||
|
ViewEncapsulation |
||||
|
} from '@angular/core'; |
||||
|
import { Store } from '@ngrx/store'; |
||||
|
import { AppState } from '@core/core.state'; |
||||
|
import { UntypedFormControl } from '@angular/forms'; |
||||
|
import { BehaviorSubject, combineLatest, debounce, Observable, of, timer } from 'rxjs'; |
||||
|
import { CdkVirtualScrollViewport } from '@angular/cdk/scrolling'; |
||||
|
import { getMaterialIcons, MaterialIcon } from '@shared/models/icon.models'; |
||||
|
import { distinctUntilChanged, map, mergeMap, share, startWith, tap } from 'rxjs/operators'; |
||||
|
import { ResourcesService } from '@core/services/resources.service'; |
||||
|
import { TbPopoverComponent } from '@shared/components/popover.component'; |
||||
|
import { BreakpointObserver } from '@angular/cdk/layout'; |
||||
|
import { MediaBreakpoints } from '@shared/models/constants'; |
||||
|
|
||||
|
@Component({ |
||||
|
selector: 'tb-material-icons', |
||||
|
templateUrl: './material-icons.component.html', |
||||
|
providers: [], |
||||
|
styleUrls: ['./material-icons.component.scss'], |
||||
|
encapsulation: ViewEncapsulation.None |
||||
|
}) |
||||
|
export class MaterialIconsComponent extends PageComponent implements OnInit { |
||||
|
|
||||
|
@ViewChild('iconsPanel') |
||||
|
iconsPanel: CdkVirtualScrollViewport; |
||||
|
|
||||
|
@Input() |
||||
|
selectedIcon: string; |
||||
|
|
||||
|
@Input() |
||||
|
popover: TbPopoverComponent<MaterialIconsComponent>; |
||||
|
|
||||
|
@Output() |
||||
|
iconSelected = new EventEmitter<string>(); |
||||
|
|
||||
|
iconRows$: Observable<MaterialIcon[][]>; |
||||
|
showAllSubject = new BehaviorSubject<boolean>(false); |
||||
|
searchIconControl: UntypedFormControl; |
||||
|
|
||||
|
iconsRowHeight = 48; |
||||
|
|
||||
|
iconsPanelHeight: string; |
||||
|
iconsPanelWidth: string; |
||||
|
|
||||
|
notFound = false; |
||||
|
|
||||
|
constructor(protected store: Store<AppState>, |
||||
|
private resourcesService: ResourcesService, |
||||
|
private breakpointObserver: BreakpointObserver, |
||||
|
private cd: ChangeDetectorRef) { |
||||
|
super(store); |
||||
|
this.searchIconControl = new UntypedFormControl(''); |
||||
|
} |
||||
|
|
||||
|
ngOnInit(): void { |
||||
|
const iconsRowSize = this.breakpointObserver.isMatched(MediaBreakpoints['lt-md']) ? 8 : 11; |
||||
|
this.calculatePanelSize(iconsRowSize); |
||||
|
const iconsRowSizeObservable = this.breakpointObserver |
||||
|
.observe(MediaBreakpoints['lt-md']).pipe( |
||||
|
map((state) => state.matches ? 8 : 11), |
||||
|
startWith(iconsRowSize), |
||||
|
); |
||||
|
this.iconRows$ = combineLatest({showAll: this.showAllSubject.asObservable(), |
||||
|
rowSize: iconsRowSizeObservable, |
||||
|
searchText: this.searchIconControl.valueChanges.pipe( |
||||
|
startWith(''), |
||||
|
debounce((searchText) => searchText ? timer(150) : of({})), |
||||
|
)}).pipe( |
||||
|
map((data) => { |
||||
|
if (data.searchText && !data.showAll) { |
||||
|
data.showAll = true; |
||||
|
this.showAllSubject.next(true); |
||||
|
} |
||||
|
return data; |
||||
|
}), |
||||
|
distinctUntilChanged((p, c) => c.showAll === p.showAll && c.searchText === p.searchText && c.rowSize === p.rowSize), |
||||
|
mergeMap((data) => getMaterialIcons(this.resourcesService, data.rowSize, data.showAll, data.searchText).pipe( |
||||
|
map(iconRows => ({iconRows, iconsRowSize: data.rowSize})) |
||||
|
)), |
||||
|
tap((data) => { |
||||
|
this.notFound = !data.iconRows.length; |
||||
|
this.calculatePanelSize(data.iconsRowSize, data.iconRows.length); |
||||
|
this.cd.markForCheck(); |
||||
|
setTimeout(() => { |
||||
|
this.checkSize(); |
||||
|
}, 0); |
||||
|
}), |
||||
|
map((data) => data.iconRows), |
||||
|
share() |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
clearSearch() { |
||||
|
this.searchIconControl.patchValue('', {emitEvent: true}); |
||||
|
} |
||||
|
|
||||
|
selectIcon(icon: MaterialIcon) { |
||||
|
this.iconSelected.emit(icon.name); |
||||
|
} |
||||
|
|
||||
|
private calculatePanelSize(iconsRowSize: number, iconRows = 4) { |
||||
|
this.iconsPanelHeight = Math.min(iconRows * this.iconsRowHeight, 10 * this.iconsRowHeight) + 'px'; |
||||
|
this.iconsPanelWidth = (iconsRowSize * 36 + (iconsRowSize - 1) * 12 + 6) + 'px'; |
||||
|
} |
||||
|
|
||||
|
private checkSize() { |
||||
|
this.iconsPanel?.checkViewportSize(); |
||||
|
this.popover?.updatePosition(); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,39 @@ |
|||||
|
<!-- |
||||
|
|
||||
|
Copyright © 2016-2023 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. |
||||
|
|
||||
|
--> |
||||
|
<mat-form-field appearance="outline" class="tb-inline-field" subscriptSizing="dynamic"> |
||||
|
<input matInput #unitInput [formControl]="unitsFormControl" |
||||
|
placeholder="{{ 'widget-config.set' | translate }}" |
||||
|
(focusin)="onFocus()" |
||||
|
[matAutocomplete]="unitsAutocomplete"> |
||||
|
<button *ngIf="unitsFormControl.value && !disabled" |
||||
|
type="button" |
||||
|
matSuffix mat-icon-button aria-label="Clear" |
||||
|
(click)="clear()"> |
||||
|
<mat-icon class="material-icons">close</mat-icon> |
||||
|
</button> |
||||
|
<mat-autocomplete |
||||
|
#unitsAutocomplete="matAutocomplete" |
||||
|
class="tb-autocomplete tb-unit-input-autocomplete" |
||||
|
panelWidth="fit-content" |
||||
|
[displayWith]="displayUnitFn"> |
||||
|
<mat-option *ngFor="let unit of filteredUnits | async" [value]="unit"> |
||||
|
<span class="tb-unit-name" fxFlex [innerHTML]="unit.name | highlight:searchText:true:'ig'"></span> |
||||
|
<span class="tb-unit-symbol" [innerHTML]="unit.symbol | highlight:searchText:true:'ig'"></span> |
||||
|
</mat-option> |
||||
|
</mat-autocomplete> |
||||
|
</mat-form-field> |
||||
@ -0,0 +1,40 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2023 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. |
||||
|
*/ |
||||
|
.tb-autocomplete.tb-unit-input-autocomplete { |
||||
|
.mat-mdc-option { |
||||
|
border-bottom: none; |
||||
|
.mdc-list-item__primary-text { |
||||
|
flex: 1; |
||||
|
display: flex; |
||||
|
flex-direction: row; |
||||
|
gap: 8px; |
||||
|
.tb-unit-name, .tb-unit-symbol { |
||||
|
font-size: 14px; |
||||
|
font-weight: 400; |
||||
|
line-height: 20px; |
||||
|
letter-spacing: 0.2px; |
||||
|
} |
||||
|
.tb-unit-symbol { |
||||
|
color: rgba(0, 0, 0, 0.38); |
||||
|
min-width: 22px; |
||||
|
text-align: end; |
||||
|
b { |
||||
|
color: rgba(0, 0, 0, 0.87); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,148 @@ |
|||||
|
///
|
||||
|
/// Copyright © 2016-2023 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, OnInit, ViewChild, ViewEncapsulation } from '@angular/core'; |
||||
|
import { ControlValueAccessor, FormControl, NG_VALUE_ACCESSOR, UntypedFormBuilder } from '@angular/forms'; |
||||
|
import { Observable, of } from 'rxjs'; |
||||
|
import { searchUnits, Unit, unitBySymbol, units } from '@shared/models/unit.models'; |
||||
|
import { map, mergeMap, startWith, tap } from 'rxjs/operators'; |
||||
|
import { TranslateService } from '@ngx-translate/core'; |
||||
|
|
||||
|
@Component({ |
||||
|
selector: 'tb-unit-input', |
||||
|
templateUrl: './unit-input.component.html', |
||||
|
styleUrls: ['./unit-input.component.scss'], |
||||
|
providers: [ |
||||
|
{ |
||||
|
provide: NG_VALUE_ACCESSOR, |
||||
|
useExisting: forwardRef(() => UnitInputComponent), |
||||
|
multi: true |
||||
|
} |
||||
|
], |
||||
|
encapsulation: ViewEncapsulation.None |
||||
|
}) |
||||
|
export class UnitInputComponent implements ControlValueAccessor, OnInit { |
||||
|
|
||||
|
unitsFormControl: FormControl; |
||||
|
|
||||
|
modelValue: string | null; |
||||
|
|
||||
|
@Input() |
||||
|
disabled: boolean; |
||||
|
|
||||
|
@ViewChild('unitInput', {static: true}) unitInput: ElementRef; |
||||
|
|
||||
|
filteredUnits: Observable<Array<Unit | string>>; |
||||
|
|
||||
|
searchText = ''; |
||||
|
|
||||
|
private dirty = false; |
||||
|
|
||||
|
private translatedUnits: Array<Unit> = units.map(u => ({symbol: u.symbol, |
||||
|
name: this.translate.instant(u.name), |
||||
|
tags: u.tags})); |
||||
|
|
||||
|
private propagateChange = (_val: any) => {}; |
||||
|
|
||||
|
constructor(private fb: UntypedFormBuilder, |
||||
|
private translate: TranslateService) { |
||||
|
} |
||||
|
|
||||
|
ngOnInit() { |
||||
|
this.unitsFormControl = this.fb.control('', []); |
||||
|
this.filteredUnits = this.unitsFormControl.valueChanges |
||||
|
.pipe( |
||||
|
tap(value => { |
||||
|
this.updateView(value); |
||||
|
}), |
||||
|
startWith<string | Unit>(''), |
||||
|
map(value => (value as Unit)?.symbol ? (value as Unit).symbol : (value ? value as string : '')), |
||||
|
mergeMap(symbol => this.fetchUnits(symbol) ) |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
writeValue(symbol?: string): void { |
||||
|
this.searchText = ''; |
||||
|
this.modelValue = symbol; |
||||
|
let res: Unit | string = null; |
||||
|
if (symbol) { |
||||
|
const unit = unitBySymbol(symbol); |
||||
|
res = unit ? unit : symbol; |
||||
|
} |
||||
|
this.unitsFormControl.patchValue(res, {emitEvent: false}); |
||||
|
this.dirty = true; |
||||
|
} |
||||
|
|
||||
|
onFocus() { |
||||
|
if (this.dirty) { |
||||
|
this.unitsFormControl.updateValueAndValidity({onlySelf: true, emitEvent: true}); |
||||
|
this.dirty = false; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
updateView(value: Unit | string | null) { |
||||
|
const res: string = (value as Unit)?.symbol ? (value as Unit)?.symbol : (value as string); |
||||
|
if (this.modelValue !== res) { |
||||
|
this.modelValue = res; |
||||
|
this.propagateChange(this.modelValue); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
displayUnitFn(unit?: Unit | string): string | undefined { |
||||
|
if (unit) { |
||||
|
if ((unit as Unit).symbol) { |
||||
|
return (unit as Unit).symbol; |
||||
|
} else { |
||||
|
return unit as string; |
||||
|
} |
||||
|
} |
||||
|
return undefined; |
||||
|
} |
||||
|
|
||||
|
fetchUnits(searchText?: string): Observable<Array<Unit | string>> { |
||||
|
this.searchText = searchText; |
||||
|
const result = searchUnits(this.translatedUnits, searchText); |
||||
|
if (result.length) { |
||||
|
return of(result); |
||||
|
} else { |
||||
|
return of([]); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
registerOnChange(fn: any): void { |
||||
|
this.propagateChange = fn; |
||||
|
} |
||||
|
|
||||
|
registerOnTouched(fn: any): void { |
||||
|
} |
||||
|
|
||||
|
setDisabledState(isDisabled: boolean): void { |
||||
|
this.disabled = isDisabled; |
||||
|
if (this.disabled) { |
||||
|
this.unitsFormControl.disable({emitEvent: false}); |
||||
|
} else { |
||||
|
this.unitsFormControl.enable({emitEvent: false}); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
clear() { |
||||
|
this.unitsFormControl.patchValue(null, {emitEvent: true}); |
||||
|
setTimeout(() => { |
||||
|
this.unitInput.nativeElement.blur(); |
||||
|
this.unitInput.nativeElement.focus(); |
||||
|
}, 0); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,72 @@ |
|||||
|
///
|
||||
|
/// Copyright © 2016-2023 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 { ResourcesService } from '@core/services/resources.service'; |
||||
|
import { Observable } from 'rxjs'; |
||||
|
import { map } from 'rxjs/operators'; |
||||
|
import { isNotEmptyStr } from '@core/utils'; |
||||
|
|
||||
|
export interface MaterialIcon { |
||||
|
name: string; |
||||
|
displayName?: string; |
||||
|
tags: string[]; |
||||
|
} |
||||
|
|
||||
|
export const iconByName = (icons: Array<MaterialIcon>, name: string): MaterialIcon => icons.find(i => i.name === name); |
||||
|
|
||||
|
const searchIconTags = (icon: MaterialIcon, searchText: string): boolean => |
||||
|
!!icon.tags.find(t => t.toUpperCase().includes(searchText.toUpperCase())); |
||||
|
|
||||
|
const searchIcons = (_icons: Array<MaterialIcon>, searchText: string): Array<MaterialIcon> => _icons.filter( |
||||
|
i => i.name.toUpperCase().includes(searchText.toUpperCase()) || |
||||
|
i.displayName.toUpperCase().includes(searchText.toUpperCase()) || |
||||
|
searchIconTags(i, searchText) |
||||
|
); |
||||
|
|
||||
|
const getCommonMaterialIcons = (icons: Array<MaterialIcon>, chunkSize: number): Array<MaterialIcon> => icons.slice(0, chunkSize * 4); |
||||
|
|
||||
|
export const getMaterialIcons = (resourcesService: ResourcesService, chunkSize = 11, |
||||
|
all = false, searchText: string): Observable<MaterialIcon[][]> => |
||||
|
resourcesService.loadJsonResource<Array<MaterialIcon>>('/assets/metadata/material-icons.json', |
||||
|
(icons) => { |
||||
|
for (const icon of icons) { |
||||
|
const words = icon.name.replace(/_/g, ' ').split(' '); |
||||
|
for (let i = 0; i < words.length; i++) { |
||||
|
words[i] = words[i].charAt(0).toUpperCase() + words[i].slice(1); |
||||
|
} |
||||
|
icon.displayName = words.join(' '); |
||||
|
} |
||||
|
return icons; |
||||
|
} |
||||
|
).pipe( |
||||
|
map((icons) => { |
||||
|
if (isNotEmptyStr(searchText)) { |
||||
|
return searchIcons(icons, searchText); |
||||
|
} else if (!all) { |
||||
|
return getCommonMaterialIcons(icons, chunkSize); |
||||
|
} else { |
||||
|
return icons; |
||||
|
} |
||||
|
}), |
||||
|
map((icons) => { |
||||
|
const iconChunks: MaterialIcon[][] = []; |
||||
|
for (let i = 0; i < icons.length; i += chunkSize) { |
||||
|
const chunk = icons.slice(i, i + chunkSize); |
||||
|
iconChunks.push(chunk); |
||||
|
} |
||||
|
return iconChunks; |
||||
|
}) |
||||
|
); |
||||
@ -0,0 +1,70 @@ |
|||||
|
///
|
||||
|
/// Copyright © 2016-2023 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.
|
||||
|
///
|
||||
|
|
||||
|
export interface Unit { |
||||
|
name: string; |
||||
|
symbol: string; |
||||
|
tags: string[]; |
||||
|
} |
||||
|
|
||||
|
export const units: Array<Unit> = [ |
||||
|
{ |
||||
|
name: 'unit.celsius', |
||||
|
symbol: '°C', |
||||
|
tags: ['temperature'] |
||||
|
}, |
||||
|
{ |
||||
|
name: 'unit.kelvin', |
||||
|
symbol: 'K', |
||||
|
tags: ['temperature'] |
||||
|
}, |
||||
|
{ |
||||
|
name: 'unit.fahrenheit', |
||||
|
symbol: '°F', |
||||
|
tags: ['temperature'] |
||||
|
}, |
||||
|
{ |
||||
|
name: 'unit.percentage', |
||||
|
symbol: '%', |
||||
|
tags: ['percentage'] |
||||
|
}, |
||||
|
{ |
||||
|
name: 'unit.second', |
||||
|
symbol: 's', |
||||
|
tags: ['time'] |
||||
|
}, |
||||
|
{ |
||||
|
name: 'unit.minute', |
||||
|
symbol: 'min', |
||||
|
tags: ['time'] |
||||
|
}, |
||||
|
{ |
||||
|
name: 'unit.hour', |
||||
|
symbol: 'h', |
||||
|
tags: ['time'] |
||||
|
} |
||||
|
]; |
||||
|
|
||||
|
export const unitBySymbol = (symbol: string): Unit => units.find(u => u.symbol === symbol); |
||||
|
|
||||
|
const searchUnitTags = (unit: Unit, searchText: string): boolean => |
||||
|
!!unit.tags.find(t => t.toUpperCase().includes(searchText.toUpperCase())); |
||||
|
|
||||
|
export const searchUnits = (_units: Array<Unit>, searchText: string): Array<Unit> => _units.filter( |
||||
|
u => u.symbol.toUpperCase().includes(searchText.toUpperCase()) || |
||||
|
u.name.toUpperCase().includes(searchText.toUpperCase()) || |
||||
|
searchUnitTags(u, searchText) |
||||
|
); |
||||
Binary file not shown.
File diff suppressed because it is too large
Loading…
Reference in new issue