22 changed files with 1237 additions and 7 deletions
@ -0,0 +1,67 @@ |
|||
<!-- |
|||
|
|||
Copyright © 2016-2020 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 [formGroup]="selectDeviceProfileFormGroup" class="mat-block"> |
|||
<input matInput type="text" placeholder="{{ 'device-profile.device-profile' | translate }}" |
|||
#deviceProfileInput |
|||
formControlName="deviceProfile" |
|||
[required]="required" |
|||
(keydown)="deviceProfileEnter($event)" |
|||
(keypress)="deviceProfileEnter($event)" |
|||
[matAutocomplete]="deviceProfileAutocomplete"> |
|||
<button *ngIf="selectDeviceProfileFormGroup.get('deviceProfile').value && !disabled" |
|||
type="button" |
|||
matSuffix mat-button mat-icon-button aria-label="Clear" |
|||
(click)="clear()"> |
|||
<mat-icon class="material-icons">close</mat-icon> |
|||
</button> |
|||
<button *ngIf="selectDeviceProfileFormGroup.get('deviceProfile').value && !disabled" |
|||
type="button" |
|||
matSuffix mat-button mat-icon-button aria-label="Edit" |
|||
matTooltip="{{ 'device-profile.edit' | translate }}" |
|||
matTooltipPosition="above" |
|||
(click)="editDeviceProfile($event)"> |
|||
<mat-icon class="material-icons">edit</mat-icon> |
|||
</button> |
|||
<mat-autocomplete |
|||
class="tb-autocomplete" |
|||
#deviceProfileAutocomplete="matAutocomplete" |
|||
[displayWith]="displayDeviceProfileFn"> |
|||
<mat-option *ngFor="let deviceProfile of filteredDeviceProfiles | async" [value]="deviceProfile"> |
|||
<span [innerHTML]="deviceProfile.name | highlight:searchText"></span> |
|||
</mat-option> |
|||
<mat-option *ngIf="!(filteredDeviceProfiles | async)?.length" [value]="null" class="tb-not-found"> |
|||
<div class="tb-not-found-content" (click)="$event.stopPropagation()"> |
|||
<div *ngIf="!textIsNotEmpty(searchText); else searchNotEmpty"> |
|||
<span translate>device-profile.no-device-profiles-found</span> |
|||
</div> |
|||
<ng-template #searchNotEmpty> |
|||
<span> |
|||
{{ translate.get('device-profile.no-device-profiles-matching', |
|||
{entity: truncate.transform(searchText, true, 6, '...')}) | async }} |
|||
</span> |
|||
</ng-template> |
|||
<span> |
|||
<a translate (click)="createDeviceProfile($event, searchText)">device-profile.create-new-device-profile</a> |
|||
</span> |
|||
</div> |
|||
</mat-option> |
|||
</mat-autocomplete> |
|||
<mat-error *ngIf="selectDeviceProfileFormGroup.get('deviceProfile').hasError('required')"> |
|||
{{ 'device-profile.device-profile-required' | translate }} |
|||
</mat-error> |
|||
</mat-form-field> |
|||
@ -0,0 +1,261 @@ |
|||
///
|
|||
/// Copyright © 2016-2020 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, EventEmitter, forwardRef, Input, OnInit, Output, ViewChild } from '@angular/core'; |
|||
import { ControlValueAccessor, FormBuilder, FormGroup, NG_VALUE_ACCESSOR } from '@angular/forms'; |
|||
import { Observable } from 'rxjs'; |
|||
import { PageLink } from '@shared/models/page/page-link'; |
|||
import { Direction } from '@shared/models/page/sort-order'; |
|||
import { map, mergeMap, startWith, tap } from 'rxjs/operators'; |
|||
import { Store } from '@ngrx/store'; |
|||
import { AppState } from '@app/core/core.state'; |
|||
import { TranslateService } from '@ngx-translate/core'; |
|||
import { coerceBooleanProperty } from '@angular/cdk/coercion'; |
|||
import { entityIdEquals } from '@shared/models/id/entity-id'; |
|||
import { TruncatePipe } from '@shared//pipe/truncate.pipe'; |
|||
import { ENTER } from '@angular/cdk/keycodes'; |
|||
import { MatDialog } from '@angular/material/dialog'; |
|||
import { DeviceProfileId } from '@shared/models/id/device-profile-id'; |
|||
import { |
|||
createDeviceProfileConfiguration, |
|||
createDeviceProfileTransportConfiguration, |
|||
DeviceProfile, |
|||
DeviceProfileInfo, |
|||
DeviceProfileType, |
|||
DeviceTransportType |
|||
} from '@shared/models/device.models'; |
|||
import { DeviceProfileService } from '@core/http/device-profile.service'; |
|||
import { DeviceProfileDialogComponent, DeviceProfileDialogData } from './device-profile-dialog.component'; |
|||
|
|||
@Component({ |
|||
selector: 'tb-device-profile-autocomplete', |
|||
templateUrl: './device-profile-autocomplete.component.html', |
|||
styleUrls: [], |
|||
providers: [{ |
|||
provide: NG_VALUE_ACCESSOR, |
|||
useExisting: forwardRef(() => DeviceProfileAutocompleteComponent), |
|||
multi: true |
|||
}] |
|||
}) |
|||
export class DeviceProfileAutocompleteComponent implements ControlValueAccessor, OnInit { |
|||
|
|||
selectDeviceProfileFormGroup: FormGroup; |
|||
|
|||
modelValue: DeviceProfileId | null; |
|||
|
|||
@Input() |
|||
selectDefaultProfile = false; |
|||
|
|||
private requiredValue: boolean; |
|||
get required(): boolean { |
|||
return this.requiredValue; |
|||
} |
|||
@Input() |
|||
set required(value: boolean) { |
|||
this.requiredValue = coerceBooleanProperty(value); |
|||
} |
|||
|
|||
@Input() |
|||
disabled: boolean; |
|||
|
|||
@Output() |
|||
deviceProfileUpdated = new EventEmitter<DeviceProfileId>(); |
|||
|
|||
@Output() |
|||
deviceProfileChanged = new EventEmitter<DeviceProfileInfo>(); |
|||
|
|||
@ViewChild('deviceProfileInput', {static: true}) deviceProfileInput: ElementRef; |
|||
|
|||
filteredDeviceProfiles: Observable<Array<DeviceProfileInfo>>; |
|||
|
|||
searchText = ''; |
|||
|
|||
private propagateChange = (v: any) => { }; |
|||
|
|||
constructor(private store: Store<AppState>, |
|||
public translate: TranslateService, |
|||
public truncate: TruncatePipe, |
|||
private deviceProfileService: DeviceProfileService, |
|||
private fb: FormBuilder, |
|||
private dialog: MatDialog) { |
|||
this.selectDeviceProfileFormGroup = this.fb.group({ |
|||
deviceProfile: [null] |
|||
}); |
|||
} |
|||
|
|||
registerOnChange(fn: any): void { |
|||
this.propagateChange = fn; |
|||
} |
|||
|
|||
registerOnTouched(fn: any): void { |
|||
} |
|||
|
|||
ngOnInit() { |
|||
this.filteredDeviceProfiles = this.selectDeviceProfileFormGroup.get('deviceProfile').valueChanges |
|||
.pipe( |
|||
tap((value: DeviceProfileInfo | string) => { |
|||
let modelValue: DeviceProfileInfo | null; |
|||
if (typeof value === 'string' || !value) { |
|||
modelValue = null; |
|||
} else { |
|||
modelValue = value; |
|||
} |
|||
this.updateView(modelValue); |
|||
}), |
|||
startWith<string | DeviceProfileInfo>(''), |
|||
map(value => value ? (typeof value === 'string' ? value : value.name) : ''), |
|||
mergeMap(name => this.fetchDeviceProfiles(name) ) |
|||
); |
|||
} |
|||
|
|||
selectDefaultDeviceProfileIfNeeded(): void { |
|||
if (this.selectDefaultProfile && !this.modelValue) { |
|||
this.deviceProfileService.getDefaultDeviceProfileInfo().subscribe( |
|||
(profile) => { |
|||
if (profile) { |
|||
this.selectDeviceProfileFormGroup.get('deviceProfile').patchValue(profile, {emitEvent: false}); |
|||
this.updateView(profile); |
|||
} |
|||
} |
|||
); |
|||
} |
|||
} |
|||
|
|||
setDisabledState(isDisabled: boolean): void { |
|||
this.disabled = isDisabled; |
|||
} |
|||
|
|||
writeValue(value: DeviceProfileId | null): void { |
|||
this.searchText = ''; |
|||
if (value != null) { |
|||
this.deviceProfileService.getDeviceProfileInfo(value.id).subscribe( |
|||
(profile) => { |
|||
this.modelValue = new DeviceProfileId(profile.id.id); |
|||
this.selectDeviceProfileFormGroup.get('deviceProfile').patchValue(profile, {emitEvent: true}); |
|||
} |
|||
); |
|||
} else { |
|||
this.modelValue = null; |
|||
this.selectDeviceProfileFormGroup.get('deviceProfile').patchValue(null, {emitEvent: true}); |
|||
this.selectDefaultDeviceProfileIfNeeded(); |
|||
} |
|||
} |
|||
|
|||
updateView(deviceProfile: DeviceProfileInfo | null) { |
|||
const idValue = deviceProfile ? new DeviceProfileId(deviceProfile.id.id) : null; |
|||
if (!entityIdEquals(this.modelValue, idValue)) { |
|||
this.modelValue = idValue; |
|||
this.propagateChange(this.modelValue); |
|||
this.deviceProfileChanged.emit(deviceProfile); |
|||
} |
|||
} |
|||
|
|||
displayDeviceProfileFn(profile?: DeviceProfileInfo): string | undefined { |
|||
return profile ? profile.name : undefined; |
|||
} |
|||
|
|||
fetchDeviceProfiles(searchText?: string): Observable<Array<DeviceProfileInfo>> { |
|||
this.searchText = searchText; |
|||
const pageLink = new PageLink(10, 0, searchText, { |
|||
property: 'name', |
|||
direction: Direction.ASC |
|||
}); |
|||
return this.deviceProfileService.getDeviceProfileInfos(pageLink, {ignoreLoading: true}).pipe( |
|||
map(pageData => { |
|||
return pageData.data; |
|||
}) |
|||
); |
|||
} |
|||
|
|||
clear() { |
|||
this.selectDeviceProfileFormGroup.get('deviceProfile').patchValue(null, {emitEvent: true}); |
|||
setTimeout(() => { |
|||
this.deviceProfileInput.nativeElement.blur(); |
|||
this.deviceProfileInput.nativeElement.focus(); |
|||
}, 0); |
|||
} |
|||
|
|||
textIsNotEmpty(text: string): boolean { |
|||
return (text && text.length > 0); |
|||
} |
|||
|
|||
deviceProfileEnter($event: KeyboardEvent) { |
|||
if ($event.keyCode === ENTER) { |
|||
$event.preventDefault(); |
|||
if (!this.modelValue) { |
|||
this.createDeviceProfile($event, this.searchText); |
|||
} |
|||
} |
|||
} |
|||
|
|||
createDeviceProfile($event: Event, profileName: string) { |
|||
$event.preventDefault(); |
|||
const deviceProfile: DeviceProfile = { |
|||
id: null, |
|||
name: profileName, |
|||
type: DeviceProfileType.DEFAULT, |
|||
transportType: DeviceTransportType.DEFAULT, |
|||
profileData: { |
|||
configuration: createDeviceProfileConfiguration(DeviceProfileType.DEFAULT), |
|||
transportConfiguration: createDeviceProfileTransportConfiguration(DeviceTransportType.DEFAULT) |
|||
} |
|||
}; |
|||
this.openDeviceProfileDialog(deviceProfile, true); |
|||
} |
|||
|
|||
editDeviceProfile($event: Event) { |
|||
$event.preventDefault(); |
|||
this.deviceProfileService.getDeviceProfile(this.modelValue.id).subscribe( |
|||
(deviceProfile) => { |
|||
this.openDeviceProfileDialog(deviceProfile, false); |
|||
} |
|||
); |
|||
} |
|||
|
|||
openDeviceProfileDialog(deviceProfile: DeviceProfile, isAdd: boolean) { |
|||
this.dialog.open<DeviceProfileDialogComponent, DeviceProfileDialogData, |
|||
DeviceProfile>(DeviceProfileDialogComponent, { |
|||
disableClose: true, |
|||
panelClass: ['tb-dialog', 'tb-fullscreen-dialog'], |
|||
data: { |
|||
isAdd, |
|||
deviceProfile |
|||
} |
|||
}).afterClosed().subscribe( |
|||
(savedDeviceProfile) => { |
|||
if (!savedDeviceProfile) { |
|||
setTimeout(() => { |
|||
this.deviceProfileInput.nativeElement.blur(); |
|||
this.deviceProfileInput.nativeElement.focus(); |
|||
}, 0); |
|||
} else { |
|||
this.deviceProfileService.getDeviceProfileInfo(savedDeviceProfile.id.id).subscribe( |
|||
(profile) => { |
|||
this.modelValue = new DeviceProfileId(profile.id.id); |
|||
this.selectDeviceProfileFormGroup.get('deviceProfile').patchValue(profile, {emitEvent: true}); |
|||
if (isAdd) { |
|||
this.propagateChange(this.modelValue); |
|||
} else { |
|||
this.deviceProfileUpdated.next(savedDeviceProfile.id); |
|||
} |
|||
this.deviceProfileChanged.emit(profile); |
|||
} |
|||
); |
|||
} |
|||
} |
|||
); |
|||
} |
|||
} |
|||
@ -0,0 +1,53 @@ |
|||
<!-- |
|||
|
|||
Copyright © 2016-2020 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. |
|||
|
|||
--> |
|||
<form (ngSubmit)="save()" style="min-width: 600px;"> |
|||
<mat-toolbar color="primary"> |
|||
<h2>{{ (isAdd ? 'device-profile.add' : 'device-profile.edit' ) | translate }}</h2> |
|||
<span fxFlex></span> |
|||
<button mat-icon-button |
|||
(click)="cancel()" |
|||
type="button"> |
|||
<mat-icon class="material-icons">close</mat-icon> |
|||
</button> |
|||
</mat-toolbar> |
|||
<mat-progress-bar color="warn" mode="indeterminate" *ngIf="isLoading$ | async"> |
|||
</mat-progress-bar> |
|||
<div style="height: 4px;" *ngIf="!(isLoading$ | async)"></div> |
|||
<div mat-dialog-content> |
|||
<tb-device-profile |
|||
#deviceProfileComponent |
|||
[standalone]="true" |
|||
[entity]="deviceProfile" |
|||
[isEdit]="true"> |
|||
</tb-device-profile> |
|||
</div> |
|||
<div mat-dialog-actions fxLayoutAlign="end center"> |
|||
<button mat-raised-button color="primary" |
|||
type="submit" |
|||
[disabled]="(isLoading$ | async) || deviceProfileComponent.entityForm?.invalid || !deviceProfileComponent.entityForm?.dirty"> |
|||
{{ (isAdd ? 'action.add' : 'action.save') | translate }} |
|||
</button> |
|||
<button mat-button color="primary" |
|||
type="button" |
|||
cdkFocusInitial |
|||
[disabled]="(isLoading$ | async)" |
|||
(click)="cancel()"> |
|||
{{ 'action.cancel' | translate }} |
|||
</button> |
|||
</div> |
|||
</form> |
|||
@ -0,0 +1,101 @@ |
|||
///
|
|||
/// Copyright © 2016-2020 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 { |
|||
AfterViewInit, |
|||
Component, |
|||
ComponentFactoryResolver, |
|||
Inject, |
|||
Injector, |
|||
SkipSelf, |
|||
ViewChild |
|||
} from '@angular/core'; |
|||
import { ErrorStateMatcher } from '@angular/material/core'; |
|||
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; |
|||
import { Store } from '@ngrx/store'; |
|||
import { AppState } from '@core/core.state'; |
|||
import { FormControl, FormGroupDirective, NgForm } from '@angular/forms'; |
|||
import { DialogComponent } from '@shared/components/dialog.component'; |
|||
import { Router } from '@angular/router'; |
|||
import { DeviceProfile } from '@shared/models/device.models'; |
|||
import { DeviceProfileComponent } from './device-profile.component'; |
|||
import { DeviceProfileService } from '@core/http/device-profile.service'; |
|||
|
|||
export interface DeviceProfileDialogData { |
|||
deviceProfile: DeviceProfile; |
|||
isAdd: boolean; |
|||
} |
|||
|
|||
@Component({ |
|||
selector: 'tb-device-profile-dialog', |
|||
templateUrl: './device-profile-dialog.component.html', |
|||
providers: [{provide: ErrorStateMatcher, useExisting: DeviceProfileDialogComponent}], |
|||
styleUrls: [] |
|||
}) |
|||
export class DeviceProfileDialogComponent extends |
|||
DialogComponent<DeviceProfileDialogComponent, DeviceProfile> implements ErrorStateMatcher, AfterViewInit { |
|||
|
|||
isAdd: boolean; |
|||
deviceProfile: DeviceProfile; |
|||
|
|||
submitted = false; |
|||
|
|||
@ViewChild('deviceProfileComponent', {static: true}) deviceProfileComponent: DeviceProfileComponent; |
|||
|
|||
constructor(protected store: Store<AppState>, |
|||
protected router: Router, |
|||
@Inject(MAT_DIALOG_DATA) public data: DeviceProfileDialogData, |
|||
public dialogRef: MatDialogRef<DeviceProfileDialogComponent, DeviceProfile>, |
|||
private componentFactoryResolver: ComponentFactoryResolver, |
|||
private injector: Injector, |
|||
@SkipSelf() private errorStateMatcher: ErrorStateMatcher, |
|||
private deviceProfileService: DeviceProfileService) { |
|||
super(store, router, dialogRef); |
|||
this.isAdd = this.data.isAdd; |
|||
this.deviceProfile = this.data.deviceProfile; |
|||
} |
|||
|
|||
ngAfterViewInit(): void { |
|||
if (this.isAdd) { |
|||
setTimeout(() => { |
|||
this.deviceProfileComponent.entityForm.markAsDirty(); |
|||
}, 0); |
|||
} |
|||
} |
|||
|
|||
isErrorState(control: FormControl | null, form: FormGroupDirective | NgForm | null): boolean { |
|||
const originalErrorState = this.errorStateMatcher.isErrorState(control, form); |
|||
const customErrorState = !!(control && control.invalid && this.submitted); |
|||
return originalErrorState || customErrorState; |
|||
} |
|||
|
|||
cancel(): void { |
|||
this.dialogRef.close(null); |
|||
} |
|||
|
|||
save(): void { |
|||
this.submitted = true; |
|||
if (this.deviceProfileComponent.entityForm.valid) { |
|||
this.deviceProfile = {...this.deviceProfile, ...this.deviceProfileComponent.entityFormValue()}; |
|||
this.deviceProfileService.saveDeviceProfile(this.deviceProfile).subscribe( |
|||
(deviceProfile) => { |
|||
this.dialogRef.close(deviceProfile); |
|||
} |
|||
); |
|||
} |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,24 @@ |
|||
<!-- |
|||
|
|||
Copyright © 2016-2020 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. |
|||
|
|||
--> |
|||
<form [formGroup]="defaultDeviceConfigurationFormGroup" style="padding-bottom: 16px;"> |
|||
<tb-json-object-edit |
|||
[required]="required" |
|||
label="{{ 'device-profile.type-default' | translate }}" |
|||
formControlName="configuration"> |
|||
</tb-json-object-edit> |
|||
</form> |
|||
@ -0,0 +1,97 @@ |
|||
///
|
|||
/// Copyright © 2016-2020 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, FormBuilder, FormGroup, NG_VALUE_ACCESSOR, Validators } from '@angular/forms'; |
|||
import { Store } from '@ngrx/store'; |
|||
import { AppState } from '@app/core/core.state'; |
|||
import { coerceBooleanProperty } from '@angular/cdk/coercion'; |
|||
import { |
|||
DefaultDeviceConfiguration, |
|||
DeviceConfiguration, |
|||
DeviceProfileType |
|||
} from '@shared/models/device.models'; |
|||
|
|||
@Component({ |
|||
selector: 'tb-default-device-configuration', |
|||
templateUrl: './default-device-configuration.component.html', |
|||
styleUrls: [], |
|||
providers: [{ |
|||
provide: NG_VALUE_ACCESSOR, |
|||
useExisting: forwardRef(() => DefaultDeviceConfigurationComponent), |
|||
multi: true |
|||
}] |
|||
}) |
|||
export class DefaultDeviceConfigurationComponent implements ControlValueAccessor, OnInit { |
|||
|
|||
defaultDeviceConfigurationFormGroup: FormGroup; |
|||
|
|||
private requiredValue: boolean; |
|||
get required(): boolean { |
|||
return this.requiredValue; |
|||
} |
|||
@Input() |
|||
set required(value: boolean) { |
|||
this.requiredValue = coerceBooleanProperty(value); |
|||
} |
|||
|
|||
@Input() |
|||
disabled: boolean; |
|||
|
|||
private propagateChange = (v: any) => { }; |
|||
|
|||
constructor(private store: Store<AppState>, |
|||
private fb: FormBuilder) { |
|||
} |
|||
|
|||
registerOnChange(fn: any): void { |
|||
this.propagateChange = fn; |
|||
} |
|||
|
|||
registerOnTouched(fn: any): void { |
|||
} |
|||
|
|||
ngOnInit() { |
|||
this.defaultDeviceConfigurationFormGroup = this.fb.group({ |
|||
configuration: [null, Validators.required] |
|||
}); |
|||
this.defaultDeviceConfigurationFormGroup.valueChanges.subscribe(() => { |
|||
this.updateModel(); |
|||
}); |
|||
} |
|||
|
|||
setDisabledState(isDisabled: boolean): void { |
|||
this.disabled = isDisabled; |
|||
if (this.disabled) { |
|||
this.defaultDeviceConfigurationFormGroup.disable({emitEvent: false}); |
|||
} else { |
|||
this.defaultDeviceConfigurationFormGroup.enable({emitEvent: false}); |
|||
} |
|||
} |
|||
|
|||
writeValue(value: DefaultDeviceConfiguration | null): void { |
|||
this.defaultDeviceConfigurationFormGroup.patchValue({configuration: value}, {emitEvent: false}); |
|||
} |
|||
|
|||
private updateModel() { |
|||
let configuration: DeviceConfiguration = null; |
|||
if (this.defaultDeviceConfigurationFormGroup.valid) { |
|||
configuration = this.defaultDeviceConfigurationFormGroup.getRawValue().configuration; |
|||
configuration.type = DeviceProfileType.DEFAULT; |
|||
} |
|||
this.propagateChange(configuration); |
|||
} |
|||
} |
|||
@ -0,0 +1,24 @@ |
|||
<!-- |
|||
|
|||
Copyright © 2016-2020 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. |
|||
|
|||
--> |
|||
<form [formGroup]="defaultDeviceTransportConfigurationFormGroup" style="padding-bottom: 16px;"> |
|||
<tb-json-object-edit |
|||
[required]="required" |
|||
label="{{ 'device-profile.transport-type-default' | translate }}" |
|||
formControlName="configuration"> |
|||
</tb-json-object-edit> |
|||
</form> |
|||
@ -0,0 +1,97 @@ |
|||
///
|
|||
/// Copyright © 2016-2020 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, FormBuilder, FormGroup, NG_VALUE_ACCESSOR, Validators } from '@angular/forms'; |
|||
import { Store } from '@ngrx/store'; |
|||
import { AppState } from '@app/core/core.state'; |
|||
import { coerceBooleanProperty } from '@angular/cdk/coercion'; |
|||
import { |
|||
DefaultDeviceTransportConfiguration, |
|||
DeviceTransportConfiguration, |
|||
DeviceTransportType |
|||
} from '@shared/models/device.models'; |
|||
|
|||
@Component({ |
|||
selector: 'tb-default-device-transport-configuration', |
|||
templateUrl: './default-device-transport-configuration.component.html', |
|||
styleUrls: [], |
|||
providers: [{ |
|||
provide: NG_VALUE_ACCESSOR, |
|||
useExisting: forwardRef(() => DefaultDeviceTransportConfigurationComponent), |
|||
multi: true |
|||
}] |
|||
}) |
|||
export class DefaultDeviceTransportConfigurationComponent implements ControlValueAccessor, OnInit { |
|||
|
|||
defaultDeviceTransportConfigurationFormGroup: FormGroup; |
|||
|
|||
private requiredValue: boolean; |
|||
get required(): boolean { |
|||
return this.requiredValue; |
|||
} |
|||
@Input() |
|||
set required(value: boolean) { |
|||
this.requiredValue = coerceBooleanProperty(value); |
|||
} |
|||
|
|||
@Input() |
|||
disabled: boolean; |
|||
|
|||
private propagateChange = (v: any) => { }; |
|||
|
|||
constructor(private store: Store<AppState>, |
|||
private fb: FormBuilder) { |
|||
} |
|||
|
|||
registerOnChange(fn: any): void { |
|||
this.propagateChange = fn; |
|||
} |
|||
|
|||
registerOnTouched(fn: any): void { |
|||
} |
|||
|
|||
ngOnInit() { |
|||
this.defaultDeviceTransportConfigurationFormGroup = this.fb.group({ |
|||
configuration: [null, Validators.required] |
|||
}); |
|||
this.defaultDeviceTransportConfigurationFormGroup.valueChanges.subscribe(() => { |
|||
this.updateModel(); |
|||
}); |
|||
} |
|||
|
|||
setDisabledState(isDisabled: boolean): void { |
|||
this.disabled = isDisabled; |
|||
if (this.disabled) { |
|||
this.defaultDeviceTransportConfigurationFormGroup.disable({emitEvent: false}); |
|||
} else { |
|||
this.defaultDeviceTransportConfigurationFormGroup.enable({emitEvent: false}); |
|||
} |
|||
} |
|||
|
|||
writeValue(value: DefaultDeviceTransportConfiguration | null): void { |
|||
this.defaultDeviceTransportConfigurationFormGroup.patchValue({configuration: value}, {emitEvent: false}); |
|||
} |
|||
|
|||
private updateModel() { |
|||
let configuration: DeviceTransportConfiguration = null; |
|||
if (this.defaultDeviceTransportConfigurationFormGroup.valid) { |
|||
configuration = this.defaultDeviceTransportConfigurationFormGroup.getRawValue().configuration; |
|||
configuration.type = DeviceTransportType.DEFAULT; |
|||
} |
|||
this.propagateChange(configuration); |
|||
} |
|||
} |
|||
@ -0,0 +1,27 @@ |
|||
<!-- |
|||
|
|||
Copyright © 2016-2020 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 [formGroup]="deviceConfigurationFormGroup"> |
|||
<div [ngSwitch]="type"> |
|||
<ng-template [ngSwitchCase]="deviceProfileType.DEFAULT"> |
|||
<tb-default-device-configuration |
|||
[required]="required" |
|||
formControlName="configuration"> |
|||
</tb-default-device-configuration> |
|||
</ng-template> |
|||
</div> |
|||
</div> |
|||
@ -0,0 +1,103 @@ |
|||
///
|
|||
/// Copyright © 2016-2020 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, FormBuilder, FormGroup, NG_VALUE_ACCESSOR, Validators } from '@angular/forms'; |
|||
import { Store } from '@ngrx/store'; |
|||
import { AppState } from '@app/core/core.state'; |
|||
import { coerceBooleanProperty } from '@angular/cdk/coercion'; |
|||
import { DeviceConfiguration, DeviceProfileType } from '@shared/models/device.models'; |
|||
import { deepClone } from '@core/utils'; |
|||
|
|||
@Component({ |
|||
selector: 'tb-device-configuration', |
|||
templateUrl: './device-configuration.component.html', |
|||
styleUrls: [], |
|||
providers: [{ |
|||
provide: NG_VALUE_ACCESSOR, |
|||
useExisting: forwardRef(() => DeviceConfigurationComponent), |
|||
multi: true |
|||
}] |
|||
}) |
|||
export class DeviceConfigurationComponent implements ControlValueAccessor, OnInit { |
|||
|
|||
deviceProfileType = DeviceProfileType; |
|||
|
|||
deviceConfigurationFormGroup: FormGroup; |
|||
|
|||
private requiredValue: boolean; |
|||
get required(): boolean { |
|||
return this.requiredValue; |
|||
} |
|||
@Input() |
|||
set required(value: boolean) { |
|||
this.requiredValue = coerceBooleanProperty(value); |
|||
} |
|||
|
|||
@Input() |
|||
disabled: boolean; |
|||
|
|||
type: DeviceProfileType; |
|||
|
|||
private propagateChange = (v: any) => { }; |
|||
|
|||
constructor(private store: Store<AppState>, |
|||
private fb: FormBuilder) { |
|||
} |
|||
|
|||
registerOnChange(fn: any): void { |
|||
this.propagateChange = fn; |
|||
} |
|||
|
|||
registerOnTouched(fn: any): void { |
|||
} |
|||
|
|||
ngOnInit() { |
|||
this.deviceConfigurationFormGroup = this.fb.group({ |
|||
configuration: [null, Validators.required] |
|||
}); |
|||
this.deviceConfigurationFormGroup.valueChanges.subscribe(() => { |
|||
this.updateModel(); |
|||
}); |
|||
} |
|||
|
|||
setDisabledState(isDisabled: boolean): void { |
|||
this.disabled = isDisabled; |
|||
if (this.disabled) { |
|||
this.deviceConfigurationFormGroup.disable({emitEvent: false}); |
|||
} else { |
|||
this.deviceConfigurationFormGroup.enable({emitEvent: false}); |
|||
} |
|||
} |
|||
|
|||
writeValue(value: DeviceConfiguration | null): void { |
|||
this.type = value?.type; |
|||
const configuration = deepClone(value); |
|||
if (configuration) { |
|||
delete configuration.type; |
|||
} |
|||
this.deviceConfigurationFormGroup.patchValue({configuration}, {emitEvent: false}); |
|||
} |
|||
|
|||
private updateModel() { |
|||
let configuration: DeviceConfiguration = null; |
|||
if (this.deviceConfigurationFormGroup.valid) { |
|||
configuration = this.deviceConfigurationFormGroup.getRawValue().configuration; |
|||
configuration.type = this.type; |
|||
} |
|||
this.propagateChange(configuration); |
|||
} |
|||
} |
|||
@ -0,0 +1,43 @@ |
|||
<!-- |
|||
|
|||
Copyright © 2016-2020 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 [formGroup]="deviceDataFormGroup" style="padding-bottom: 16px;"> |
|||
<mat-accordion multi="true"> |
|||
<mat-expansion-panel [expanded]="true"> |
|||
<mat-expansion-panel-header> |
|||
<mat-panel-title> |
|||
<div translate>device.device-configuration</div> |
|||
</mat-panel-title> |
|||
</mat-expansion-panel-header> |
|||
<tb-device-configuration |
|||
formControlName="configuration" |
|||
required> |
|||
</tb-device-configuration> |
|||
</mat-expansion-panel> |
|||
<mat-expansion-panel [expanded]="true"> |
|||
<mat-expansion-panel-header> |
|||
<mat-panel-title> |
|||
<div translate>device.transport-configuration</div> |
|||
</mat-panel-title> |
|||
</mat-expansion-panel-header> |
|||
<tb-device-transport-configuration |
|||
formControlName="transportConfiguration" |
|||
required> |
|||
</tb-device-transport-configuration> |
|||
</mat-expansion-panel> |
|||
</mat-accordion> |
|||
</div> |
|||
@ -0,0 +1,94 @@ |
|||
///
|
|||
/// Copyright © 2016-2020 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, FormBuilder, FormGroup, NG_VALUE_ACCESSOR, Validators } from '@angular/forms'; |
|||
import { Store } from '@ngrx/store'; |
|||
import { AppState } from '@app/core/core.state'; |
|||
import { coerceBooleanProperty } from '@angular/cdk/coercion'; |
|||
import { DeviceData } from '@shared/models/device.models'; |
|||
|
|||
@Component({ |
|||
selector: 'tb-device-data', |
|||
templateUrl: './device-data.component.html', |
|||
styleUrls: [], |
|||
providers: [{ |
|||
provide: NG_VALUE_ACCESSOR, |
|||
useExisting: forwardRef(() => DeviceDataComponent), |
|||
multi: true |
|||
}] |
|||
}) |
|||
export class DeviceDataComponent implements ControlValueAccessor, OnInit { |
|||
|
|||
deviceDataFormGroup: FormGroup; |
|||
|
|||
private requiredValue: boolean; |
|||
get required(): boolean { |
|||
return this.requiredValue; |
|||
} |
|||
@Input() |
|||
set required(value: boolean) { |
|||
this.requiredValue = coerceBooleanProperty(value); |
|||
} |
|||
|
|||
@Input() |
|||
disabled: boolean; |
|||
|
|||
private propagateChange = (v: any) => { }; |
|||
|
|||
constructor(private store: Store<AppState>, |
|||
private fb: FormBuilder) { |
|||
} |
|||
|
|||
registerOnChange(fn: any): void { |
|||
this.propagateChange = fn; |
|||
} |
|||
|
|||
registerOnTouched(fn: any): void { |
|||
} |
|||
|
|||
ngOnInit() { |
|||
this.deviceDataFormGroup = this.fb.group({ |
|||
configuration: [null, Validators.required], |
|||
transportConfiguration: [null, Validators.required] |
|||
}); |
|||
this.deviceDataFormGroup.valueChanges.subscribe(() => { |
|||
this.updateModel(); |
|||
}); |
|||
} |
|||
|
|||
setDisabledState(isDisabled: boolean): void { |
|||
this.disabled = isDisabled; |
|||
if (this.disabled) { |
|||
this.deviceDataFormGroup.disable({emitEvent: false}); |
|||
} else { |
|||
this.deviceDataFormGroup.enable({emitEvent: false}); |
|||
} |
|||
} |
|||
|
|||
writeValue(value: DeviceData | null): void { |
|||
this.deviceDataFormGroup.patchValue({configuration: value?.configuration}, {emitEvent: false}); |
|||
this.deviceDataFormGroup.patchValue({transportConfiguration: value?.transportConfiguration}, {emitEvent: false}); |
|||
} |
|||
|
|||
private updateModel() { |
|||
let deviceData: DeviceData = null; |
|||
if (this.deviceDataFormGroup.valid) { |
|||
deviceData = this.deviceDataFormGroup.getRawValue(); |
|||
} |
|||
this.propagateChange(deviceData); |
|||
} |
|||
} |
|||
@ -0,0 +1,27 @@ |
|||
<!-- |
|||
|
|||
Copyright © 2016-2020 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 [formGroup]="deviceTransportConfigurationFormGroup"> |
|||
<div [ngSwitch]="transportType"> |
|||
<ng-template [ngSwitchCase]="deviceTransportType.DEFAULT"> |
|||
<tb-default-device-transport-configuration |
|||
[required]="required" |
|||
formControlName="configuration"> |
|||
</tb-default-device-transport-configuration> |
|||
</ng-template> |
|||
</div> |
|||
</div> |
|||
@ -0,0 +1,106 @@ |
|||
///
|
|||
/// Copyright © 2016-2020 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, FormBuilder, FormGroup, NG_VALUE_ACCESSOR, Validators } from '@angular/forms'; |
|||
import { Store } from '@ngrx/store'; |
|||
import { AppState } from '@app/core/core.state'; |
|||
import { coerceBooleanProperty } from '@angular/cdk/coercion'; |
|||
import { |
|||
DeviceTransportConfiguration, |
|||
DeviceTransportType |
|||
} from '@shared/models/device.models'; |
|||
import { deepClone } from '@core/utils'; |
|||
|
|||
@Component({ |
|||
selector: 'tb-device-transport-configuration', |
|||
templateUrl: './device-transport-configuration.component.html', |
|||
styleUrls: [], |
|||
providers: [{ |
|||
provide: NG_VALUE_ACCESSOR, |
|||
useExisting: forwardRef(() => DeviceTransportConfigurationComponent), |
|||
multi: true |
|||
}] |
|||
}) |
|||
export class DeviceTransportConfigurationComponent implements ControlValueAccessor, OnInit { |
|||
|
|||
deviceTransportType = DeviceTransportType; |
|||
|
|||
deviceTransportConfigurationFormGroup: FormGroup; |
|||
|
|||
private requiredValue: boolean; |
|||
get required(): boolean { |
|||
return this.requiredValue; |
|||
} |
|||
@Input() |
|||
set required(value: boolean) { |
|||
this.requiredValue = coerceBooleanProperty(value); |
|||
} |
|||
|
|||
@Input() |
|||
disabled: boolean; |
|||
|
|||
transportType: DeviceTransportType; |
|||
|
|||
private propagateChange = (v: any) => { }; |
|||
|
|||
constructor(private store: Store<AppState>, |
|||
private fb: FormBuilder) { |
|||
} |
|||
|
|||
registerOnChange(fn: any): void { |
|||
this.propagateChange = fn; |
|||
} |
|||
|
|||
registerOnTouched(fn: any): void { |
|||
} |
|||
|
|||
ngOnInit() { |
|||
this.deviceTransportConfigurationFormGroup = this.fb.group({ |
|||
configuration: [null, Validators.required] |
|||
}); |
|||
this.deviceTransportConfigurationFormGroup.valueChanges.subscribe(() => { |
|||
this.updateModel(); |
|||
}); |
|||
} |
|||
|
|||
setDisabledState(isDisabled: boolean): void { |
|||
this.disabled = isDisabled; |
|||
if (this.disabled) { |
|||
this.deviceTransportConfigurationFormGroup.disable({emitEvent: false}); |
|||
} else { |
|||
this.deviceTransportConfigurationFormGroup.enable({emitEvent: false}); |
|||
} |
|||
} |
|||
|
|||
writeValue(value: DeviceTransportConfiguration | null): void { |
|||
this.transportType = value?.type; |
|||
const configuration = deepClone(value); |
|||
if (configuration) { |
|||
delete configuration.type; |
|||
} |
|||
this.deviceTransportConfigurationFormGroup.patchValue({configuration}, {emitEvent: false}); |
|||
} |
|||
|
|||
private updateModel() { |
|||
let configuration: DeviceTransportConfiguration = null; |
|||
if (this.deviceTransportConfigurationFormGroup.valid) { |
|||
configuration = this.deviceTransportConfigurationFormGroup.getRawValue().configuration; |
|||
configuration.type = this.transportType; |
|||
} |
|||
this.propagateChange(configuration); |
|||
} |
|||
} |
|||
Loading…
Reference in new issue