committed by
GitHub
21 changed files with 346 additions and 108 deletions
@ -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,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,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) |
|||
); |
|||
Loading…
Reference in new issue