Browse Source

Merge branch 'develop/3.5.2' into improvement/move-form-table-style

pull/8897/head
Igor Kulikov 3 years ago
committed by GitHub
parent
commit
afcebf2eda
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      ui-ngx/src/app/modules/common/modules-map.ts
  2. 2
      ui-ngx/src/app/modules/home/components/alias/entity-alias-select.component.html
  3. 4
      ui-ngx/src/app/modules/home/components/widget/config/basic/cards/simple-card-basic-config.component.html
  4. 4
      ui-ngx/src/app/modules/home/components/widget/config/basic/common/data-key-row.component.html
  5. 9
      ui-ngx/src/app/modules/home/components/widget/config/basic/common/data-key-row.component.scss
  6. 5
      ui-ngx/src/app/modules/home/components/widget/config/basic/common/data-keys-panel.component.scss
  7. 4
      ui-ngx/src/app/modules/home/components/widget/config/data-key-config.component.html
  8. 3
      ui-ngx/src/app/modules/home/components/widget/config/widget-config-components.module.ts
  9. 20
      ui-ngx/src/app/modules/home/components/widget/config/widget-units.component.html
  10. 68
      ui-ngx/src/app/modules/home/components/widget/config/widget-units.component.ts
  11. 4
      ui-ngx/src/app/modules/home/components/widget/widget-config.component.html
  12. 2
      ui-ngx/src/app/shared/components/entity/entity-autocomplete.component.html
  13. 1
      ui-ngx/src/app/shared/components/public-api.ts
  14. 39
      ui-ngx/src/app/shared/components/unit-input.component.html
  15. 40
      ui-ngx/src/app/shared/components/unit-input.component.scss
  16. 148
      ui-ngx/src/app/shared/components/unit-input.component.ts
  17. 70
      ui-ngx/src/app/shared/models/unit.models.ts
  18. 5
      ui-ngx/src/app/shared/pipe/highlight.pipe.ts
  19. 3
      ui-ngx/src/app/shared/shared.module.ts
  20. 9
      ui-ngx/src/assets/locale/locale.constant-en_US.json
  21. 12
      ui-ngx/src/form.scss

2
ui-ngx/src/app/modules/common/modules-map.ts

@ -180,6 +180,7 @@ import * as SlackConversationAutocompleteComponent from '@shared/components/slac
import * as StringItemsListComponent from '@shared/components/string-items-list.component';
import * as ToggleHeaderComponent from '@shared/components/toggle-header.component';
import * as ToggleSelectComponent from '@shared/components/toggle-select.component';
import * as UnitInputComponent from '@shared/components/unit-input.component';
import * as AddEntityDialogComponent from '@home/components/entity/add-entity-dialog.component';
import * as EntitiesTableComponent from '@home/components/entity/entities-table.component';
@ -480,6 +481,7 @@ class ModulesMap implements IModulesMap {
'@shared/components/string-items-list.component': StringItemsListComponent,
'@shared/components/toggle-header.component': ToggleHeaderComponent,
'@shared/components/toggle-select.component': ToggleSelectComponent,
'@shared/components/unit-input.component': UnitInputComponent,
'@home/components/entity/add-entity-dialog.component': AddEntityDialogComponent,
'@home/components/entity/entities-table.component': EntitiesTableComponent,

2
ui-ngx/src/app/modules/home/components/alias/entity-alias-select.component.html

@ -42,7 +42,7 @@
#entityAliasAutocomplete="matAutocomplete"
[displayWith]="displayEntityAliasFn">
<mat-option *ngFor="let entityAlias of filteredEntityAliases | async" [value]="entityAlias">
<span [innerHTML]="entityAlias.alias | highlight:searchText"></span>
<span [innerHTML]="entityAlias.alias | highlight:searchText:true:'ig'"></span>
</mat-option>
<mat-option *ngIf="!(filteredEntityAliases | async)?.length" [value]="null" class="tb-not-found">
<div class="tb-not-found-content" (click)="$event.stopPropagation()">

4
ui-ngx/src/app/modules/home/components/widget/config/basic/cards/simple-card-basic-config.component.html

@ -51,9 +51,9 @@
</div>
<div class="tb-form-row space-between">
<div translate>widget-config.units-short</div>
<tb-widget-units
<tb-unit-input
formControlName="units">
</tb-widget-units>
</tb-unit-input>
</div>
<div class="tb-form-row space-between">
<div translate>widget-config.decimals-short</div>

4
ui-ngx/src/app/modules/home/components/widget/config/basic/common/data-key-row.component.html

@ -148,9 +148,9 @@
</tb-color-input>
</div>
<div *ngIf="!hideUnits" class="tb-units-field">
<tb-widget-units *ngIf="displayUnitsOrDigits"
<tb-unit-input *ngIf="displayUnitsOrDigits"
formControlName="units">
</tb-widget-units>
</tb-unit-input>
</div>
<div *ngIf="!hideDecimals" class="tb-decimals-field">
<mat-form-field *ngIf="displayUnitsOrDigits" appearance="outline" class="tb-inline-field number" subscriptSizing="dynamic">

9
ui-ngx/src/app/modules/home/components/widget/config/basic/common/data-key-row.component.scss

@ -42,10 +42,17 @@
}
.tb-color-field, .tb-units-field, .tb-decimals-field {
width: 60px;
display: flex;
flex-direction: row;
place-content: center;
align-items: center;
}
.tb-units-field {
width: 80px;
}
.tb-color-field, .tb-decimals-field {
width: 60px;
}
}

5
ui-ngx/src/app/modules/home/components/widget/config/basic/common/data-keys-panel.component.scss

@ -17,7 +17,10 @@
&.tb-source-header {
width: 140px;
}
&.tb-color-header, &.tb-units-header, &.tb-decimals-header {
&.tb-units-header {
width: 80px;
}
&.tb-color-header, &.tb-decimals-header {
width: 60px;
}
&.tb-actions-header {

4
ui-ngx/src/app/modules/home/components/widget/config/data-key-config.component.html

@ -48,9 +48,9 @@
<ng-container *ngIf="modelValue.type !== dataKeyTypes.alarm">
<div class="tb-form-row space-between" *ngIf="!hideDataKeyUnits">
<div translate>widget-config.units-short</div>
<tb-widget-units
<tb-unit-input
formControlName="units">
</tb-widget-units>
</tb-unit-input>
</div>
<div class="tb-form-row space-between" *ngIf="!hideDataKeyDecimals">
<div translate>widget-config.decimals-short</div>

3
ui-ngx/src/app/modules/home/components/widget/config/widget-config-components.module.ts

@ -29,7 +29,6 @@ import { FilterSelectComponent } from '@home/components/filter/filter-select.com
import { WidgetSettingsModule } from '@home/components/widget/lib/settings/widget-settings.module';
import { WidgetSettingsComponent } from '@home/components/widget/config/widget-settings.component';
import { TimewindowConfigPanelComponent } from '@home/components/widget/config/timewindow-config-panel.component';
import { WidgetUnitsComponent } from '@home/components/widget/config/widget-units.component';
@NgModule({
declarations:
@ -44,7 +43,6 @@ import { WidgetUnitsComponent } from '@home/components/widget/config/widget-unit
EntityAliasSelectComponent,
FilterSelectComponent,
TimewindowConfigPanelComponent,
WidgetUnitsComponent,
WidgetSettingsComponent
],
imports: [
@ -63,7 +61,6 @@ import { WidgetUnitsComponent } from '@home/components/widget/config/widget-unit
EntityAliasSelectComponent,
FilterSelectComponent,
TimewindowConfigPanelComponent,
WidgetUnitsComponent,
WidgetSettingsComponent
]
})

20
ui-ngx/src/app/modules/home/components/widget/config/widget-units.component.html

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

68
ui-ngx/src/app/modules/home/components/widget/config/widget-units.component.ts

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

4
ui-ngx/src/app/modules/home/components/widget/widget-config.component.html

@ -267,9 +267,9 @@
<div class="tb-form-panel-title" translate>widget-config.data-settings</div>
<div *ngIf="displayUnitsConfig" class="tb-form-row space-between">
<div translate>widget-config.units</div>
<tb-widget-units
<tb-unit-input
formControlName="units">
</tb-widget-units>
</tb-unit-input>
</div>
<div *ngIf="displayUnitsConfig" class="tb-form-row space-between">
<div translate>widget-config.decimals</div>

2
ui-ngx/src/app/shared/components/entity/entity-autocomplete.component.html

@ -33,7 +33,7 @@
#entityAutocomplete="matAutocomplete"
[displayWith]="displayEntityFn">
<mat-option *ngFor="let entity of filteredEntities | async" [value]="entity">
<span [innerHTML]="entity.name | highlight:searchText"></span>
<span [innerHTML]="entity.name | highlight:searchText:true:'ig'"></span>
</mat-option>
<mat-option *ngIf="!(filteredEntities | async)?.length" [value]="null">
<span>

1
ui-ngx/src/app/shared/components/public-api.ts

@ -25,3 +25,4 @@ export * from './notification/template-autocomplete.component';
export * from './resource/resource-autocomplete.component';
export * from './toggle-header.component';
export * from './toggle-select.component';
export * from './unit-input.component';

39
ui-ngx/src/app/shared/components/unit-input.component.html

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

40
ui-ngx/src/app/shared/components/unit-input.component.scss

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

148
ui-ngx/src/app/shared/components/unit-input.component.ts

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

70
ui-ngx/src/app/shared/models/unit.models.ts

@ -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)
);

5
ui-ngx/src/app/shared/pipe/highlight.pipe.ts

@ -18,11 +18,10 @@ import { Pipe, PipeTransform } from '@angular/core';
@Pipe({ name: 'highlight' })
export class HighlightPipe implements PipeTransform {
transform(text: string, search): string {
transform(text: string, search: string, includes = false, flags = 'i'): string {
const pattern = search
.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, '\\$&');
const regex = new RegExp('^' + pattern, 'i');
const regex = new RegExp((!includes ? '^' : '') + pattern, flags);
return search ? text.replace(regex, match => `<b>${match}</b>`) : text;
}
}

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

@ -194,6 +194,7 @@ import { ShortNumberPipe } from '@shared/pipe/short-number.pipe';
import { ToggleHeaderComponent, ToggleOption } from '@shared/components/toggle-header.component';
import { RuleChainSelectComponent } from '@shared/components/rule-chain/rule-chain-select.component';
import { ToggleSelectComponent } from '@shared/components/toggle-select.component';
import { UnitInputComponent } from '@shared/components/unit-input.component';
export function MarkedOptionsFactory(markedOptionsService: MarkedOptionsService) {
return markedOptionsService;
@ -367,6 +368,7 @@ export function MarkedOptionsFactory(markedOptionsService: MarkedOptionsService)
ToggleHeaderComponent,
ToggleOption,
ToggleSelectComponent,
UnitInputComponent,
RuleChainSelectComponent
],
imports: [
@ -597,6 +599,7 @@ export function MarkedOptionsFactory(markedOptionsService: MarkedOptionsService)
ToggleHeaderComponent,
ToggleOption,
ToggleSelectComponent,
UnitInputComponent,
RuleChainSelectComponent
]
})

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

@ -3860,6 +3860,15 @@
"just-now": "Just now",
"ago": "ago"
},
"unit": {
"celsius": "Celsius",
"kelvin": "Kelvin",
"fahrenheit": "Fahrenheit",
"percentage": "Percentage",
"second": "Second",
"minute": "Minute",
"hour": "Hour"
},
"user": {
"user": "User",
"users": "Users",

12
ui-ngx/src/form.scss

@ -177,9 +177,15 @@
opacity: 0;
}
}
&:not(.mat-mdc-form-field-has-icon-suffix) {
.mat-mdc-text-field-wrapper {
&.mdc-text-field--outlined, &:not(.mdc-text-field--outlined) {
padding-right: 12px;
}
}
}
.mat-mdc-text-field-wrapper {
&.mdc-text-field--outlined, &:not(.mdc-text-field--outlined) {
padding-right: 12px;
padding-left: 12px;
&:not(.mdc-text-field--focused):not(.mdc-text-field--disabled):not(:hover) {
.mdc-notched-outline__leading, .mdc-notched-outline__trailing {
@ -233,7 +239,9 @@
}
&.number {
.mat-mdc-text-field-wrapper {
padding-right: 4px;
&.mdc-text-field--outlined, &:not(.mdc-text-field--outlined) {
padding-right: 4px;
}
.mat-mdc-form-field-infix {
input.mdc-text-field__input[type=number]::-webkit-inner-spin-button,
input.mdc-text-field__input[type=number]::-webkit-outer-spin-button {

Loading…
Cancel
Save