Merge pull request #12150 from vvlladd28/feature/edit-alias/datasource-widgets

Add edit alias from dasource widgets
This commit is contained in:
Igor Kulikov 2024-11-29 18:06:58 +02:00 committed by GitHub
commit 404ab062a0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 97 additions and 59 deletions

View File

@ -31,6 +31,14 @@
(click)="clear()">
<mat-icon class="material-icons">close</mat-icon>
</button>
<button *ngIf="selectEntityAliasFormGroup.get('entityAlias').value?.id && !disabled"
type="button"
matSuffix mat-icon-button aria-label="Edit"
matTooltip="{{ 'device-profile.edit' | translate }}"
matTooltipPosition="above"
(click)="editEntityAlias($event)">
<mat-icon class="material-icons">edit</mat-icon>
</button>
<button *ngIf="!selectEntityAliasFormGroup.get('entityAlias').value && !disabled"
style="margin-right: 8px;"
type="button"

View File

@ -20,4 +20,5 @@ import { EntityAlias } from '@shared/models/alias.models';
export interface EntityAliasSelectCallbacks {
createEntityAlias: (alias: string, allowedEntityTypes: Array<EntityType>) => Observable<EntityAlias>;
editEntityAlias: (alias: EntityAlias, allowedEntityTypes: Array<EntityType>) => Observable<EntityAlias>;
}

View File

@ -14,24 +14,22 @@
/// limitations under the License.
///
import { AfterViewInit, Component, ElementRef, forwardRef, Input, OnInit, SkipSelf, ViewChild } from '@angular/core';
import { Component, ElementRef, forwardRef, Input, OnInit, SkipSelf, ViewChild } from '@angular/core';
import {
ControlValueAccessor,
UntypedFormBuilder,
UntypedFormControl,
UntypedFormGroup,
FormBuilder,
FormControl,
FormGroup,
FormGroupDirective,
NG_VALUE_ACCESSOR,
NgForm
NgForm,
} from '@angular/forms';
import { Observable, of } from 'rxjs';
import { map, mergeMap, share, tap } from 'rxjs/operators';
import { Store } from '@ngrx/store';
import { AppState } from '@app/core/core.state';
import { TranslateService } from '@ngx-translate/core';
import { EntityType } from '@shared/models/entity-type.models';
import { EntityService } from '@core/http/entity.service';
import { coerceBooleanProperty } from '@angular/cdk/coercion';
import { coerceBoolean } from '@shared/decorators/coercion';
import { EntityAlias } from '@shared/models/alias.models';
import { IAliasController } from '@core/api/widget-api.models';
import { TruncatePipe } from '@shared/pipe/truncate.pipe';
@ -54,9 +52,9 @@ import { ErrorStateMatcher } from '@angular/material/core';
useExisting: EntityAliasSelectComponent
}*/]
})
export class EntityAliasSelectComponent implements ControlValueAccessor, OnInit, AfterViewInit, ErrorStateMatcher {
export class EntityAliasSelectComponent implements ControlValueAccessor, OnInit, ErrorStateMatcher {
selectEntityAliasFormGroup: UntypedFormGroup;
selectEntityAliasFormGroup: FormGroup;
modelValue: string | null;
@ -75,15 +73,9 @@ export class EntityAliasSelectComponent implements ControlValueAccessor, OnInit,
@ViewChild('entityAliasAutocomplete') entityAliasAutocomplete: MatAutocomplete;
@ViewChild('autocomplete', { read: MatAutocompleteTrigger }) autoCompleteTrigger: MatAutocompleteTrigger;
private requiredValue: boolean;
get tbRequired(): boolean {
return this.requiredValue;
}
@Input()
set tbRequired(value: boolean) {
this.requiredValue = coerceBooleanProperty(value);
}
@coerceBoolean()
tbRequired: boolean;
@Input()
disabled: boolean;
@ -98,16 +90,13 @@ export class EntityAliasSelectComponent implements ControlValueAccessor, OnInit,
private dirty = false;
private creatingEntityAlias = false;
private propagateChange = (_v: any) => { };
private propagateChange = (v: any) => { };
constructor(private store: Store<AppState>,
@SkipSelf() private errorStateMatcher: ErrorStateMatcher,
constructor(@SkipSelf() private errorStateMatcher: ErrorStateMatcher,
private entityService: EntityService,
public translate: TranslateService,
public truncate: TruncatePipe,
private fb: UntypedFormBuilder) {
private fb: FormBuilder) {
this.selectEntityAliasFormGroup = this.fb.group({
entityAlias: [null]
});
@ -117,7 +106,7 @@ export class EntityAliasSelectComponent implements ControlValueAccessor, OnInit,
this.propagateChange = fn;
}
registerOnTouched(fn: any): void {
registerOnTouched(_fn: any): void {
}
ngOnInit() {
@ -134,7 +123,7 @@ export class EntityAliasSelectComponent implements ControlValueAccessor, OnInit,
this.filteredEntityAliases = this.selectEntityAliasFormGroup.get('entityAlias').valueChanges
.pipe(
tap(value => {
let modelValue;
let modelValue: EntityAlias;
if (typeof value === 'string' || !value) {
modelValue = null;
} else {
@ -151,14 +140,12 @@ export class EntityAliasSelectComponent implements ControlValueAccessor, OnInit,
);
}
isErrorState(control: UntypedFormControl | null, form: FormGroupDirective | NgForm | null): boolean {
isErrorState(control: FormControl | null, form: FormGroupDirective | NgForm | null): boolean {
const originalErrorState = this.errorStateMatcher.isErrorState(control, form);
const customErrorState = this.tbRequired && !this.modelValue;
return originalErrorState || customErrorState;
}
ngAfterViewInit(): void {}
setDisabledState(isDisabled: boolean): void {
this.disabled = isDisabled;
if (this.disabled) {
@ -225,7 +212,7 @@ export class EntityAliasSelectComponent implements ControlValueAccessor, OnInit,
}
textIsNotEmpty(text: string): boolean {
return (text && text != null && text.length > 0) ? true : false;
return text?.length > 0;
}
entityAliasEnter($event: KeyboardEvent) {
@ -237,10 +224,23 @@ export class EntityAliasSelectComponent implements ControlValueAccessor, OnInit,
}
}
editEntityAlias($event: Event) {
$event.preventDefault();
$event.stopPropagation();
if (this.callbacks && this.callbacks.editEntityAlias) {
this.callbacks.editEntityAlias(this.selectEntityAliasFormGroup.get('entityAlias').value,
this.allowedEntityTypes).subscribe((alias) => {
if (alias) {
this.modelValue = alias.id;
this.selectEntityAliasFormGroup.get('entityAlias').patchValue(alias, {emitEvent: true});
}
});
}
}
createEntityAlias($event: Event, alias: string, focusOnCancel = true) {
$event.preventDefault();
$event.stopPropagation();
this.creatingEntityAlias = true;
if (this.callbacks && this.callbacks.createEntityAlias) {
this.callbacks.createEntityAlias(alias, this.allowedEntityTypes).subscribe((newAlias) => {
if (!newAlias) {

View File

@ -136,8 +136,7 @@
</div>
<ng-template #searchNotEmpty>
<span>
{{ translate.get('entity.no-key-matching',
{key: truncate.transform(searchText, true, 6, &apos;...&apos;)}) | async }}
{{ 'entity.no-key-matching' | translate : {key: searchText | truncate: true: 6: &apos;...&apos;} }}
</span>
<span *ngIf="!isEntityDatasource; else createEntityKey">
<a translate (click)="createKey(searchText)">entity.create-new-key</a>

View File

@ -16,8 +16,8 @@
import { COMMA, ENTER, SEMICOLON } from '@angular/cdk/keycodes';
import {
ChangeDetectorRef,
Component,
DestroyRef,
ElementRef,
forwardRef,
Input,
@ -33,20 +33,18 @@ import {
import {
AbstractControl,
ControlValueAccessor,
FormBuilder,
FormControl,
FormGroup,
FormGroupDirective,
NG_VALIDATORS,
NG_VALUE_ACCESSOR,
NgForm,
UntypedFormBuilder,
UntypedFormControl,
UntypedFormGroup,
ValidationErrors,
Validator
} from '@angular/forms';
import { Observable, of } from 'rxjs';
import { filter, map, mergeMap, publishReplay, refCount, share, tap } from 'rxjs/operators';
import { Store } from '@ngrx/store';
import { AppState } from '@app/core/core.state';
import { Observable, of, ReplaySubject } from 'rxjs';
import { filter, map, mergeMap, share, tap } from 'rxjs/operators';
import { TranslateService } from '@ngx-translate/core';
import { MatAutocomplete, MatAutocompleteTrigger } from '@angular/material/autocomplete';
import { MatChipGrid, MatChipInputEvent, MatChipRow } from '@angular/material/chips';
@ -58,7 +56,6 @@ import { DataKeySettingsFunction } from './data-keys.component.models';
import { alarmFields } from '@shared/models/alarm.models';
import { UtilsService } from '@core/services/utils.service';
import { ErrorStateMatcher } from '@angular/material/core';
import { TruncatePipe } from '@shared/pipe/truncate.pipe';
import { MatDialog } from '@angular/material/dialog';
import {
DataKeyConfigDialogComponent,
@ -74,6 +71,7 @@ import { DatasourceComponent } from '@home/components/widget/config/datasource.c
import { ColorPickerPanelComponent } from '@shared/components/color-picker/color-picker-panel.component';
import { TbPopoverService } from '@shared/components/popover.service';
import { WidgetConfigCallbacks } from '@home/components/widget/config/widget-config.component.models';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
@Component({
selector: 'tb-data-keys',
@ -115,11 +113,10 @@ export class DataKeysComponent implements ControlValueAccessor, OnInit, OnChange
return this.datasourceComponent.hideDataKeyDecimals;
}
datasourceTypes = DatasourceType;
widgetTypes = widgetType;
dataKeyTypes = DataKeyType;
keysListFormGroup: UntypedFormGroup;
keysListFormGroup: FormGroup;
modelValue: Array<DataKey> | null;
@ -218,23 +215,21 @@ export class DataKeysComponent implements ControlValueAccessor, OnInit, OnChange
private dirty = false;
private propagateChange = (v: any) => { };
private propagateChange = (_v: any) => { };
private keysRequired = this._keysRequired.bind(this);
private keysValidator = this._keysValidator.bind(this);
constructor(private store: Store<AppState>,
@SkipSelf() private errorStateMatcher: ErrorStateMatcher,
constructor(@SkipSelf() private errorStateMatcher: ErrorStateMatcher,
private datasourceComponent: DatasourceComponent,
public translate: TranslateService,
private translate: TranslateService,
private utils: UtilsService,
private dialog: MatDialog,
private fb: UntypedFormBuilder,
private cd: ChangeDetectorRef,
private fb: FormBuilder,
private popoverService: TbPopoverService,
private viewContainerRef: ViewContainerRef,
private renderer: Renderer2,
public truncate: TruncatePipe) {
private destroyRef: DestroyRef) {
}
updateValidators() {
@ -246,7 +241,7 @@ export class DataKeysComponent implements ControlValueAccessor, OnInit, OnChange
this.keysListFormGroup.get('keys').updateValueAndValidity();
}
private _keysRequired(control: AbstractControl): ValidationErrors | null {
private _keysRequired(_control: AbstractControl): ValidationErrors | null {
const value = this.modelValue;
if (value && Array.isArray(value) && value.length) {
return null;
@ -255,7 +250,7 @@ export class DataKeysComponent implements ControlValueAccessor, OnInit, OnChange
}
}
private _keysValidator(control: AbstractControl): ValidationErrors | null {
private _keysValidator(_control: AbstractControl): ValidationErrors | null {
const value = this.modelValue;
if (value && Array.isArray(value)) {
if (value.some(v => isObject(v) && (!v.type || !v.name))) {
@ -274,7 +269,7 @@ export class DataKeysComponent implements ControlValueAccessor, OnInit, OnChange
}
}
registerOnTouched(fn: any): void {
registerOnTouched(_fn: any): void {
}
ngOnInit() {
@ -314,6 +309,15 @@ export class DataKeysComponent implements ControlValueAccessor, OnInit, OnChange
mergeMap(name => this.fetchKeys(name) ),
share()
);
this.aliasController.entityAliasesChanged.pipe(
takeUntilDestroyed(this.destroyRef)
).subscribe(aliasIds => {
if (aliasIds.includes(this.entityAliasId)) {
this.clearSearchCache();
this.dirty = true;
}
})
}
public maxDataKeysText(): string {
@ -413,7 +417,7 @@ export class DataKeysComponent implements ControlValueAccessor, OnInit, OnChange
}
}
isErrorState(control: UntypedFormControl | null, form: FormGroupDirective | NgForm | null): boolean {
isErrorState(control: FormControl | null, form: FormGroupDirective | NgForm | null): boolean {
const originalErrorState = this.errorStateMatcher.isErrorState(control, form);
const customErrorState = this.keysListFormGroup.get('keys').hasError('dataKey');
return originalErrorState || customErrorState;
@ -442,7 +446,7 @@ export class DataKeysComponent implements ControlValueAccessor, OnInit, OnChange
this.dirty = true;
}
validate(c: UntypedFormControl) {
validate(_c: FormControl) {
return (this.keysListFormGroup.get('keys').hasError('dataKey')) ? {
dataKeys: {
valid: false,
@ -637,8 +641,12 @@ export class DataKeysComponent implements ControlValueAccessor, OnInit, OnChange
fetchObservable = of([]);
}
this.fetchObservable$ = fetchObservable.pipe(
publishReplay(1),
refCount()
share({
connector: () => new ReplaySubject(1),
resetOnError: false,
resetOnComplete: false,
resetOnRefCountZero: false
})
);
}
return this.fetchObservable$;
@ -650,7 +658,7 @@ export class DataKeysComponent implements ControlValueAccessor, OnInit, OnChange
}
textIsNotEmpty(text: string): boolean {
return text && text.length > 0;
return text?.length > 0;
}
clear(value: string = '', focus = true) {

View File

@ -165,6 +165,7 @@ export class WidgetConfigComponent extends PageComponent implements OnInit, OnDe
widgetConfigCallbacks: WidgetConfigCallbacks = {
createEntityAlias: this.createEntityAlias.bind(this),
editEntityAlias: this.editEntityAlias.bind(this),
createFilter: this.createFilter.bind(this),
generateDataKey: this.generateDataKey.bind(this),
fetchEntityKeysForDevice: this.fetchEntityKeysForDevice.bind(this),
@ -841,6 +842,27 @@ export class WidgetConfigComponent extends PageComponent implements OnInit, OnDe
);
}
private editEntityAlias(alias: EntityAlias, allowedEntityTypes: Array<EntityType>): Observable<EntityAlias> {
return this.dialog.open<EntityAliasDialogComponent, EntityAliasDialogData,
EntityAlias>(EntityAliasDialogComponent, {
disableClose: true,
panelClass: ['tb-dialog', 'tb-fullscreen-dialog'],
data: {
isAdd: false,
allowedEntityTypes,
entityAliases: this.dashboard.configuration.entityAliases,
alias: deepClone(alias)
}
}).afterClosed().pipe(
tap((entityAlias) => {
if (entityAlias) {
this.dashboard.configuration.entityAliases[entityAlias.id] = entityAlias;
this.aliasController.updateEntityAliases(this.dashboard.configuration.entityAliases);
}
})
);
}
private createFilter(filter: string): Observable<Filter> {
const singleFilter: Filter = {id: null, filter, keyFilters: [], editable: true};
return this.dialog.open<FilterDialogComponent, FilterDialogData,