Showing
9 changed files
with
250 additions
and
146 deletions
... | ... | @@ -85,7 +85,7 @@ |
85 | 85 | </mat-step> |
86 | 86 | <mat-step [stepControl]="alarmRulesFormGroup"> |
87 | 87 | <form [formGroup]="alarmRulesFormGroup" style="padding-bottom: 16px;"> |
88 | - <ng-template matStepLabel>{{'device-profile.alarm-rules' | translate: | |
88 | + <ng-template matStepLabel>{{'device-profile.alarm-rules-with-count' | translate: | |
89 | 89 | {count: alarmRulesFormGroup.get('alarms').value ? |
90 | 90 | alarmRulesFormGroup.get('alarms').value.length : 0} }}</ng-template> |
91 | 91 | <tb-device-profile-alarms | ... | ... |
... | ... | @@ -20,9 +20,9 @@ import { |
20 | 20 | EventEmitter, |
21 | 21 | forwardRef, |
22 | 22 | Input, |
23 | - NgZone, | |
23 | + NgZone, OnChanges, | |
24 | 24 | OnInit, |
25 | - Output, | |
25 | + Output, SimpleChanges, | |
26 | 26 | ViewChild |
27 | 27 | } from '@angular/core'; |
28 | 28 | import { ControlValueAccessor, FormBuilder, FormGroup, NG_VALUE_ACCESSOR } from '@angular/forms'; |
... | ... | @@ -55,7 +55,7 @@ import { AddDeviceProfileDialogComponent, AddDeviceProfileDialogData } from './a |
55 | 55 | multi: true |
56 | 56 | }] |
57 | 57 | }) |
58 | -export class DeviceProfileAutocompleteComponent implements ControlValueAccessor, OnInit { | |
58 | +export class DeviceProfileAutocompleteComponent implements ControlValueAccessor, OnInit, OnChanges { | |
59 | 59 | |
60 | 60 | selectDeviceProfileFormGroup: FormGroup; |
61 | 61 | |
... | ... | @@ -168,11 +168,22 @@ export class DeviceProfileAutocompleteComponent implements ControlValueAccessor, |
168 | 168 | ); |
169 | 169 | } |
170 | 170 | |
171 | + ngOnChanges(changes: SimpleChanges): void { | |
172 | + for (const propName of Object.keys(changes)) { | |
173 | + const change = changes[propName]; | |
174 | + if (!change.firstChange && change.currentValue !== change.previousValue) { | |
175 | + if (propName === 'transportType') { | |
176 | + this.writeValue(null); | |
177 | + } | |
178 | + } | |
179 | + } | |
180 | + } | |
181 | + | |
171 | 182 | selectDefaultDeviceProfileIfNeeded(): void { |
172 | 183 | if (this.selectDefaultProfile && !this.modelValue) { |
173 | 184 | this.deviceProfileService.getDefaultDeviceProfileInfo().subscribe( |
174 | 185 | (profile) => { |
175 | - if (profile) { | |
186 | + if (profile && !this.transportType || (profile.transportType === this.transportType)) { | |
176 | 187 | this.selectDeviceProfileFormGroup.get('deviceProfile').patchValue(profile, {emitEvent: false}); |
177 | 188 | this.updateView(profile); |
178 | 189 | } | ... | ... |
... | ... | @@ -42,7 +42,7 @@ |
42 | 42 | <mat-expansion-panel [expanded]="true"> |
43 | 43 | <mat-expansion-panel-header> |
44 | 44 | <mat-panel-title> |
45 | - <div>{{'device-profile.alarm-rules' | translate: | |
45 | + <div>{{'device-profile.alarm-rules-with-count' | translate: | |
46 | 46 | {count: deviceProfileDataFormGroup.get('alarms').value ? |
47 | 47 | deviceProfileDataFormGroup.get('alarms').value.length : 0} }}</div> |
48 | 48 | </mat-panel-title> | ... | ... |
... | ... | @@ -15,7 +15,7 @@ |
15 | 15 | limitations under the License. |
16 | 16 | |
17 | 17 | --> |
18 | -<div style="min-width: 1000px;"> | |
18 | +<div> | |
19 | 19 | <mat-toolbar color="primary"> |
20 | 20 | <h2 translate>device.add-device-text</h2> |
21 | 21 | <span fxFlex></span> |
... | ... | @@ -29,7 +29,7 @@ |
29 | 29 | </mat-progress-bar> |
30 | 30 | <div style="height: 4px;" *ngIf="!(isLoading$ | async)"></div> |
31 | 31 | <div mat-dialog-content> |
32 | - <mat-horizontal-stepper [linear]="true" #addDeviceWizardStepper (selectionChange)="changeStep($event)"> | |
32 | + <mat-horizontal-stepper [linear]="true" [labelPosition]="labelPosition" #addDeviceWizardStepper (selectionChange)="changeStep($event)"> | |
33 | 33 | <ng-template matStepperIcon="edit"> |
34 | 34 | <mat-icon>check</mat-icon> |
35 | 35 | </ng-template> |
... | ... | @@ -48,60 +48,60 @@ |
48 | 48 | <mat-label translate>device.label</mat-label> |
49 | 49 | <input matInput formControlName="label"> |
50 | 50 | </mat-form-field> |
51 | - <mat-form-field class="mat-block"> | |
51 | + <mat-form-field class="mat-block" style="padding-bottom: 14px;"> | |
52 | 52 | <mat-label translate>device-profile.transport-type</mat-label> |
53 | 53 | <mat-select formControlName="transportType" required> |
54 | 54 | <mat-option *ngFor="let type of deviceTransportTypes" [value]="type"> |
55 | 55 | {{deviceTransportTypeTranslations.get(type) | translate}} |
56 | 56 | </mat-option> |
57 | 57 | </mat-select> |
58 | + <mat-hint *ngIf="deviceWizardFormGroup.get('transportType').value"> | |
59 | + {{deviceTransportTypeHints.get(deviceWizardFormGroup.get('transportType').value) | translate}} | |
60 | + </mat-hint> | |
58 | 61 | <mat-error *ngIf="deviceWizardFormGroup.get('transportType').hasError('required')"> |
59 | 62 | {{ 'device-profile.transport-type-required' | translate }} |
60 | 63 | </mat-error> |
61 | 64 | </mat-form-field> |
62 | - <mat-checkbox formControlName="gateway" style="padding-bottom: 16px;"> | |
63 | - {{ 'device.is-gateway' | translate }} | |
64 | - </mat-checkbox> | |
65 | - <mat-form-field class="mat-block"> | |
66 | - <mat-label translate>device.description</mat-label> | |
67 | - <textarea matInput formControlName="description" rows="2"></textarea> | |
68 | - </mat-form-field> | |
69 | - </fieldset> | |
70 | - </form> | |
71 | - </mat-step> | |
72 | - <mat-step [stepControl]="profileConfigFormGroup"> | |
73 | - <form [formGroup]="profileConfigFormGroup" style="padding-bottom: 16px;"> | |
74 | - <ng-template matStepLabel>{{ 'device.wizard.profile-configuration' | translate}}</ng-template> | |
75 | - <mat-radio-group fxLayout="column" fxFlex formControlName="addProfileType"> | |
76 | - <mat-radio-button [value]="0" color="primary"> | |
77 | - <section> | |
78 | - <span translate>device.wizard.existing-device-profile</span> | |
65 | + <div fxLayout="row" fxLayoutGap="16px"> | |
66 | + <mat-radio-group fxLayout="column" formControlName="addProfileType" fxLayoutAlign="space-around"> | |
67 | + <mat-radio-button [value]="0" color="primary"> | |
68 | + <span translate>device.wizard.existing-device-profile</span> | |
69 | + </mat-radio-button> | |
70 | + <mat-radio-button [value]="1" color="primary"> | |
71 | + <span translate>device.wizard.new-device-profile</span> | |
72 | + </mat-radio-button> | |
73 | + </mat-radio-group> | |
74 | + <div fxLayout="column"> | |
79 | 75 | <tb-device-profile-autocomplete |
80 | - [required]="profileConfigFormGroup.get('addProfileType').value === 0" | |
76 | + [required]="!createProfile" | |
81 | 77 | [transportType]="deviceWizardFormGroup.get('transportType').value" |
82 | 78 | formControlName="deviceProfileId" |
79 | + (deviceProfileChanged)="$event?.transportType ? deviceWizardFormGroup.get('transportType').patchValue($event?.transportType) : {}" | |
83 | 80 | [addNewProfile]="false" |
81 | + [selectDefaultProfile]="true" | |
84 | 82 | [editProfileEnabled]="false"> |
85 | 83 | </tb-device-profile-autocomplete> |
86 | - </section> | |
87 | - </mat-radio-button> | |
88 | - <mat-radio-button [value]="1" color="primary"> | |
89 | - <section fxLayout="column"> | |
90 | - <span translate>device.wizard.new-device-profile</span> | |
91 | 84 | <mat-form-field fxFlex class="mat-block"> |
92 | - <mat-label translate>device-profile.device-profile</mat-label> | |
85 | + <mat-label translate>device-profile.new-device-profile-name</mat-label> | |
93 | 86 | <input matInput formControlName="newDeviceProfileTitle" |
94 | - [required]="profileConfigFormGroup.get('addProfileType').value === 1"> | |
95 | - <mat-error *ngIf="profileConfigFormGroup.get('newDeviceProfileTitle').hasError('required')"> | |
96 | - {{ 'device-profile.device-profile-required' | translate }} | |
87 | + [required]="createProfile"> | |
88 | + <mat-error *ngIf="deviceWizardFormGroup.get('newDeviceProfileTitle').hasError('required')"> | |
89 | + {{ 'device-profile.new-device-profile-name-required' | translate }} | |
97 | 90 | </mat-error> |
98 | 91 | </mat-form-field> |
99 | - </section> | |
100 | - </mat-radio-button> | |
101 | - </mat-radio-group> | |
92 | + </div> | |
93 | + </div> | |
94 | + <mat-checkbox formControlName="gateway" style="padding-bottom: 16px;"> | |
95 | + {{ 'device.is-gateway' | translate }} | |
96 | + </mat-checkbox> | |
97 | + <mat-form-field class="mat-block"> | |
98 | + <mat-label translate>device.description</mat-label> | |
99 | + <textarea matInput formControlName="description" rows="2"></textarea> | |
100 | + </mat-form-field> | |
101 | + </fieldset> | |
102 | 102 | </form> |
103 | 103 | </mat-step> |
104 | - <mat-step [stepControl]="transportConfigFormGroup" *ngIf="createdProfile"> | |
104 | + <mat-step [stepControl]="transportConfigFormGroup" *ngIf="createTransportConfiguration"> | |
105 | 105 | <form [formGroup]="transportConfigFormGroup" style="padding-bottom: 16px;"> |
106 | 106 | <ng-template matStepLabel>{{ 'device-profile.transport-configuration' | translate }}</ng-template> |
107 | 107 | <tb-device-profile-transport-configuration |
... | ... | @@ -110,9 +110,9 @@ |
110 | 110 | </tb-device-profile-transport-configuration> |
111 | 111 | </form> |
112 | 112 | </mat-step> |
113 | - <mat-step [stepControl]="alarmRulesFormGroup" *ngIf="createdProfile"> | |
113 | + <mat-step [stepControl]="alarmRulesFormGroup" [optional]="true" *ngIf="createProfile"> | |
114 | 114 | <form [formGroup]="alarmRulesFormGroup" style="padding-bottom: 16px;"> |
115 | - <ng-template matStepLabel>{{'device-profile.alarm-rules' | translate: | |
115 | + <ng-template matStepLabel>{{'device-profile.alarm-rules-with-count' | translate: | |
116 | 116 | {count: alarmRulesFormGroup.get('alarms').value ? |
117 | 117 | alarmRulesFormGroup.get('alarms').value.length : 0} }}</ng-template> |
118 | 118 | <tb-device-profile-alarms |
... | ... | @@ -120,36 +120,50 @@ |
120 | 120 | </tb-device-profile-alarms> |
121 | 121 | </form> |
122 | 122 | </mat-step> |
123 | - <mat-step [stepControl]="specificConfigFormGroup"> | |
124 | - <ng-template matStepLabel>{{ 'device.wizard.specific-configuration' | translate }}</ng-template> | |
125 | - <form [formGroup]="specificConfigFormGroup" style="padding-bottom: 16px;"> | |
123 | + <mat-step [stepControl]="credentialsFormGroup" [optional]="true"> | |
124 | + <ng-template matStepLabel>{{ 'device.credentials' | translate }}</ng-template> | |
125 | + <form [formGroup]="credentialsFormGroup" style="padding-bottom: 16px;"> | |
126 | + <mat-checkbox style="padding-bottom: 16px;" formControlName="setCredential">{{ 'device.wizard.add-credential' | translate }}</mat-checkbox> | |
127 | + <tb-device-credentials | |
128 | + [fxShow]="credentialsFormGroup.get('setCredential').value" | |
129 | + formControlName="credential"> | |
130 | + </tb-device-credentials> | |
131 | + </form> | |
132 | + </mat-step> | |
133 | + <mat-step [stepControl]="customerFormGroup" [optional]="true"> | |
134 | + <ng-template matStepLabel>{{ 'customer.customer' | translate }}</ng-template> | |
135 | + <form [formGroup]="customerFormGroup" style="padding-bottom: 16px;"> | |
126 | 136 | <tb-entity-autocomplete |
127 | 137 | formControlName="customerId" |
128 | 138 | labelText="device.wizard.customer-to-assign-device" |
129 | 139 | [entityType]="entityType.CUSTOMER"> |
130 | 140 | </tb-entity-autocomplete> |
131 | - <mat-checkbox formControlName="setCredential">{{ 'device.wizard.add-credential' | translate }}</mat-checkbox> | |
132 | - <tb-device-credentials | |
133 | - [fxShow]="specificConfigFormGroup.get('setCredential').value" | |
134 | - formControlName="credential"> | |
135 | - </tb-device-credentials> | |
136 | 141 | </form> |
137 | 142 | </mat-step> |
138 | 143 | </mat-horizontal-stepper> |
139 | 144 | </div> |
140 | - <div mat-dialog-actions fxLayout="row wrap" fxLayoutAlign="space-between center"> | |
141 | - <button mat-button *ngIf="selectedIndex > 0" | |
142 | - [disabled]="(isLoading$ | async)" | |
143 | - (click)="previousStep()">{{ 'action.back' | translate }}</button> | |
144 | - <span *ngIf="selectedIndex == 0"></span> | |
145 | - <div fxLayout="row wrap" fxLayoutGap="20px"> | |
145 | + <div mat-dialog-actions fxLayout="column" fxLayoutAlign="start wrap" fxLayoutGap="8px" style="height: 100px;"> | |
146 | + <div fxFlex fxLayout="row" fxLayoutAlign="end"> | |
147 | + <button mat-raised-button | |
148 | + *ngIf="showNext" | |
149 | + [disabled]="(isLoading$ | async)" | |
150 | + (click)="nextStep()">{{ 'action.next-with-label' | translate:{label: (getFormLabel(this.selectedIndex+1) | translate)} }}</button> | |
151 | + </div> | |
152 | + <div fxFlex fxLayout="row"> | |
146 | 153 | <button mat-button |
154 | + color="primary" | |
147 | 155 | [disabled]="(isLoading$ | async)" |
148 | 156 | (click)="cancel()">{{ 'action.cancel' | translate }}</button> |
149 | - <button mat-raised-button | |
150 | - [disabled]="(isLoading$ | async) || selectedForm.invalid" | |
151 | - color="primary" | |
152 | - (click)="nextStep()">{{ nextStepButtonLabel$ | async | translate }}</button> | |
157 | + <span fxFlex></span> | |
158 | + <div fxLayout="row wrap" fxLayoutGap="8px"> | |
159 | + <button mat-raised-button *ngIf="selectedIndex > 0" | |
160 | + [disabled]="(isLoading$ | async)" | |
161 | + (click)="previousStep()">{{ 'action.back' | translate }}</button> | |
162 | + <button mat-raised-button | |
163 | + [disabled]="(isLoading$ | async)" | |
164 | + color="primary" | |
165 | + (click)="add()">{{ 'action.add' | translate }}</button> | |
166 | + </div> | |
153 | 167 | </div> |
154 | 168 | </div> |
155 | 169 | </div> | ... | ... |
... | ... | @@ -13,25 +13,51 @@ |
13 | 13 | * See the License for the specific language governing permissions and |
14 | 14 | * limitations under the License. |
15 | 15 | */ |
16 | -:host { | |
17 | - .mat-dialog-content { | |
18 | - display: flex; | |
19 | - flex-direction: column; | |
20 | - overflow: hidden; | |
21 | 16 | |
22 | - .mat-stepper-horizontal { | |
23 | - display: flex; | |
24 | - flex-direction: column; | |
25 | - overflow: hidden; | |
17 | +@import "../../../../../scss/constants"; | |
18 | + | |
19 | +:host-context(.tb-fullscreen-dialog .mat-dialog-container) { | |
20 | + @media #{$mat-lt-sm} { | |
21 | + .mat-dialog-content { | |
22 | + max-height: 75vh; | |
26 | 23 | } |
27 | 24 | } |
28 | 25 | } |
29 | 26 | |
30 | 27 | :host ::ng-deep { |
31 | 28 | .mat-dialog-content { |
29 | + display: flex; | |
30 | + flex-direction: column; | |
31 | + height: 100%; | |
32 | + | |
32 | 33 | .mat-stepper-horizontal { |
34 | + display: flex; | |
35 | + flex-direction: column; | |
36 | + height: 100%; | |
37 | + overflow: hidden; | |
38 | + @media #{$mat-lt-sm} { | |
39 | + .mat-step-label { | |
40 | + white-space: normal; | |
41 | + overflow: visible; | |
42 | + .mat-step-text-label { | |
43 | + overflow: visible; | |
44 | + } | |
45 | + } | |
46 | + } | |
33 | 47 | .mat-horizontal-content-container { |
34 | - overflow: auto; | |
48 | + height: 450px; | |
49 | + max-height: 100%; | |
50 | + width: 100%;; | |
51 | + overflow-y: auto; | |
52 | + @media #{$mat-gt-sm} { | |
53 | + min-width: 800px; | |
54 | + } | |
55 | + } | |
56 | + .mat-horizontal-stepper-content[aria-expanded=true] { | |
57 | + height: 100%; | |
58 | + form { | |
59 | + height: 100%; | |
60 | + } | |
35 | 61 | } |
36 | 62 | } |
37 | 63 | } | ... | ... |
... | ... | @@ -26,7 +26,7 @@ import { |
26 | 26 | createDeviceProfileTransportConfiguration, |
27 | 27 | DeviceProfile, |
28 | 28 | DeviceProfileType, |
29 | - DeviceTransportType, | |
29 | + DeviceTransportType, deviceTransportTypeHintMap, | |
30 | 30 | deviceTransportTypeTranslationMap |
31 | 31 | } from '@shared/models/device.models'; |
32 | 32 | import { MatHorizontalStepper } from '@angular/material/stepper'; |
... | ... | @@ -35,11 +35,13 @@ import { BaseData, HasId } from '@shared/models/base-data'; |
35 | 35 | import { EntityType } from '@shared/models/entity-type.models'; |
36 | 36 | import { DeviceProfileService } from '@core/http/device-profile.service'; |
37 | 37 | import { EntityId } from '@shared/models/id/entity-id'; |
38 | -import { BehaviorSubject, Observable, of, Subscription } from 'rxjs'; | |
38 | +import { Observable, of, Subscription } from 'rxjs'; | |
39 | 39 | import { map, mergeMap, tap } from 'rxjs/operators'; |
40 | 40 | import { DeviceService } from '@core/http/device.service'; |
41 | 41 | import { ErrorStateMatcher } from '@angular/material/core'; |
42 | 42 | import { StepperSelectionEvent } from '@angular/cdk/stepper'; |
43 | +import { BreakpointObserver, BreakpointState } from '@angular/cdk/layout'; | |
44 | +import { MediaBreakpoints } from '@shared/models/constants'; | |
43 | 45 | |
44 | 46 | @Component({ |
45 | 47 | selector: 'tb-device-wizard', |
... | ... | @@ -54,9 +56,10 @@ export class DeviceWizardDialogComponent extends |
54 | 56 | |
55 | 57 | selectedIndex = 0; |
56 | 58 | |
57 | - nextStepButtonLabel$ = new BehaviorSubject<string>('action.continue'); | |
59 | + showNext = true; | |
58 | 60 | |
59 | - createdProfile = false; | |
61 | + createProfile = false; | |
62 | + createTransportConfiguration = false; | |
60 | 63 | |
61 | 64 | entityType = EntityType; |
62 | 65 | |
... | ... | @@ -64,15 +67,19 @@ export class DeviceWizardDialogComponent extends |
64 | 67 | |
65 | 68 | deviceTransportTypeTranslations = deviceTransportTypeTranslationMap; |
66 | 69 | |
67 | - deviceWizardFormGroup: FormGroup; | |
70 | + deviceTransportTypeHints = deviceTransportTypeHintMap; | |
68 | 71 | |
69 | - profileConfigFormGroup: FormGroup; | |
72 | + deviceWizardFormGroup: FormGroup; | |
70 | 73 | |
71 | 74 | transportConfigFormGroup: FormGroup; |
72 | 75 | |
73 | 76 | alarmRulesFormGroup: FormGroup; |
74 | 77 | |
75 | - specificConfigFormGroup: FormGroup; | |
78 | + credentialsFormGroup: FormGroup; | |
79 | + | |
80 | + customerFormGroup: FormGroup; | |
81 | + | |
82 | + labelPosition = 'end'; | |
76 | 83 | |
77 | 84 | private subscriptions: Subscription[] = []; |
78 | 85 | |
... | ... | @@ -83,6 +90,7 @@ export class DeviceWizardDialogComponent extends |
83 | 90 | public dialogRef: MatDialogRef<DeviceWizardDialogComponent, boolean>, |
84 | 91 | private deviceProfileService: DeviceProfileService, |
85 | 92 | private deviceService: DeviceService, |
93 | + private breakpointObserver: BreakpointObserver, | |
86 | 94 | private fb: FormBuilder) { |
87 | 95 | super(store, router, dialogRef); |
88 | 96 | this.deviceWizardFormGroup = this.fb.group({ |
... | ... | @@ -90,33 +98,32 @@ export class DeviceWizardDialogComponent extends |
90 | 98 | label: [''], |
91 | 99 | gateway: [false], |
92 | 100 | transportType: [DeviceTransportType.DEFAULT, Validators.required], |
93 | - description: [''] | |
94 | - } | |
95 | - ); | |
96 | - | |
97 | - this.profileConfigFormGroup = this.fb.group({ | |
98 | 101 | addProfileType: [0], |
99 | 102 | deviceProfileId: [null, Validators.required], |
100 | - newDeviceProfileTitle: [{value: null, disabled: true}] | |
103 | + newDeviceProfileTitle: [{value: null, disabled: true}], | |
104 | + description: [''] | |
101 | 105 | } |
102 | 106 | ); |
103 | 107 | |
104 | - this.subscriptions.push(this.profileConfigFormGroup.get('addProfileType').valueChanges.subscribe( | |
108 | + this.subscriptions.push(this.deviceWizardFormGroup.get('addProfileType').valueChanges.subscribe( | |
105 | 109 | (addProfileType: number) => { |
106 | 110 | if (addProfileType === 0) { |
107 | - this.profileConfigFormGroup.get('deviceProfileId').setValidators([Validators.required]); | |
108 | - this.profileConfigFormGroup.get('deviceProfileId').enable(); | |
109 | - this.profileConfigFormGroup.get('newDeviceProfileTitle').setValidators(null); | |
110 | - this.profileConfigFormGroup.get('newDeviceProfileTitle').disable(); | |
111 | - this.profileConfigFormGroup.updateValueAndValidity(); | |
112 | - this.createdProfile = false; | |
111 | + this.deviceWizardFormGroup.get('deviceProfileId').setValidators([Validators.required]); | |
112 | + this.deviceWizardFormGroup.get('deviceProfileId').enable(); | |
113 | + this.deviceWizardFormGroup.get('newDeviceProfileTitle').setValidators(null); | |
114 | + this.deviceWizardFormGroup.get('newDeviceProfileTitle').disable(); | |
115 | + this.deviceWizardFormGroup.updateValueAndValidity(); | |
116 | + this.createProfile = false; | |
117 | + this.createTransportConfiguration = false; | |
113 | 118 | } else { |
114 | - this.profileConfigFormGroup.get('deviceProfileId').setValidators(null); | |
115 | - this.profileConfigFormGroup.get('deviceProfileId').disable(); | |
116 | - this.profileConfigFormGroup.get('newDeviceProfileTitle').setValidators([Validators.required]); | |
117 | - this.profileConfigFormGroup.get('newDeviceProfileTitle').enable(); | |
118 | - this.profileConfigFormGroup.updateValueAndValidity(); | |
119 | - this.createdProfile = true; | |
119 | + this.deviceWizardFormGroup.get('deviceProfileId').setValidators(null); | |
120 | + this.deviceWizardFormGroup.get('deviceProfileId').disable(); | |
121 | + this.deviceWizardFormGroup.get('newDeviceProfileTitle').setValidators([Validators.required]); | |
122 | + this.deviceWizardFormGroup.get('newDeviceProfileTitle').enable(); | |
123 | + this.deviceWizardFormGroup.updateValueAndValidity(); | |
124 | + this.createProfile = true; | |
125 | + this.createTransportConfiguration = this.deviceWizardFormGroup.get('transportType').value && | |
126 | + DeviceTransportType.DEFAULT !== this.deviceWizardFormGroup.get('transportType').value; | |
120 | 127 | } |
121 | 128 | } |
122 | 129 | )); |
... | ... | @@ -135,20 +142,37 @@ export class DeviceWizardDialogComponent extends |
135 | 142 | } |
136 | 143 | ); |
137 | 144 | |
138 | - this.specificConfigFormGroup = this.fb.group({ | |
139 | - customerId: [null], | |
145 | + this.credentialsFormGroup = this.fb.group({ | |
140 | 146 | setCredential: [false], |
141 | 147 | credential: [{value: null, disabled: true}] |
142 | 148 | } |
143 | 149 | ); |
144 | 150 | |
145 | - this.subscriptions.push(this.specificConfigFormGroup.get('setCredential').valueChanges.subscribe((value) => { | |
151 | + this.subscriptions.push(this.credentialsFormGroup.get('setCredential').valueChanges.subscribe((value) => { | |
146 | 152 | if (value) { |
147 | - this.specificConfigFormGroup.get('credential').enable(); | |
153 | + this.credentialsFormGroup.get('credential').enable(); | |
148 | 154 | } else { |
149 | - this.specificConfigFormGroup.get('credential').disable(); | |
155 | + this.credentialsFormGroup.get('credential').disable(); | |
150 | 156 | } |
151 | 157 | })); |
158 | + | |
159 | + this.customerFormGroup = this.fb.group({ | |
160 | + customerId: [null] | |
161 | + } | |
162 | + ); | |
163 | + | |
164 | + this.labelPosition = this.breakpointObserver.isMatched(MediaBreakpoints['gt-sm']) ? 'end' : 'bottom'; | |
165 | + | |
166 | + this.subscriptions.push(this.breakpointObserver | |
167 | + .observe(MediaBreakpoints['gt-sm']) | |
168 | + .subscribe((state: BreakpointState) => { | |
169 | + if (state.matches) { | |
170 | + this.labelPosition = 'end'; | |
171 | + } else { | |
172 | + this.labelPosition = 'bottom'; | |
173 | + } | |
174 | + } | |
175 | + )); | |
152 | 176 | } |
153 | 177 | |
154 | 178 | ngOnDestroy() { |
... | ... | @@ -171,26 +195,28 @@ export class DeviceWizardDialogComponent extends |
171 | 195 | } |
172 | 196 | |
173 | 197 | nextStep(): void { |
174 | - if (this.selectedIndex < this.maxStepperIndex) { | |
175 | - this.addDeviceWizardStepper.next(); | |
176 | - } else { | |
177 | - this.add(); | |
178 | - } | |
198 | + this.addDeviceWizardStepper.next(); | |
179 | 199 | } |
180 | 200 | |
181 | - get selectedForm(): FormGroup { | |
182 | - const index = !this.createdProfile && this.selectedIndex === this.maxStepperIndex ? 4 : this.selectedIndex; | |
201 | + getFormLabel(index: number): string { | |
202 | + if (index > 0) { | |
203 | + if (!this.createProfile) { | |
204 | + index += 2; | |
205 | + } else if (!this.createTransportConfiguration) { | |
206 | + index += 1; | |
207 | + } | |
208 | + } | |
183 | 209 | switch (index) { |
184 | 210 | case 0: |
185 | - return this.deviceWizardFormGroup; | |
211 | + return 'device.wizard.device-details'; | |
186 | 212 | case 1: |
187 | - return this.profileConfigFormGroup; | |
213 | + return 'device-profile.transport-configuration'; | |
188 | 214 | case 2: |
189 | - return this.transportConfigFormGroup; | |
215 | + return 'device-profile.alarm-rules'; | |
190 | 216 | case 3: |
191 | - return this.alarmRulesFormGroup; | |
217 | + return 'device.credentials'; | |
192 | 218 | case 4: |
193 | - return this.specificConfigFormGroup; | |
219 | + return 'customer.customer'; | |
194 | 220 | } |
195 | 221 | } |
196 | 222 | |
... | ... | @@ -201,23 +227,27 @@ export class DeviceWizardDialogComponent extends |
201 | 227 | private deviceProfileTransportTypeChanged(deviceTransportType: DeviceTransportType): void { |
202 | 228 | this.transportConfigFormGroup.patchValue( |
203 | 229 | {transportConfiguration: createDeviceProfileTransportConfiguration(deviceTransportType)}); |
230 | + this.createTransportConfiguration = this.createProfile && deviceTransportType && | |
231 | + DeviceTransportType.DEFAULT !== deviceTransportType; | |
204 | 232 | } |
205 | 233 | |
206 | - private add(): void { | |
207 | - this.creatProfile().pipe( | |
208 | - mergeMap(profileId => this.createdDevice(profileId)), | |
209 | - mergeMap(device => this.saveCredential(device)) | |
210 | - ).subscribe( | |
211 | - (created) => { | |
212 | - this.dialogRef.close(created); | |
213 | - } | |
214 | - ); | |
234 | + add(): void { | |
235 | + if (this.allValid()) { | |
236 | + this.createDeviceProfile().pipe( | |
237 | + mergeMap(profileId => this.createDevice(profileId)), | |
238 | + mergeMap(device => this.saveCredentials(device)) | |
239 | + ).subscribe( | |
240 | + (created) => { | |
241 | + this.dialogRef.close(created); | |
242 | + } | |
243 | + ); | |
244 | + } | |
215 | 245 | } |
216 | 246 | |
217 | - private creatProfile(): Observable<EntityId> { | |
218 | - if (this.profileConfigFormGroup.get('addProfileType').value) { | |
247 | + private createDeviceProfile(): Observable<EntityId> { | |
248 | + if (this.deviceWizardFormGroup.get('addProfileType').value) { | |
219 | 249 | const deviceProfile: DeviceProfile = { |
220 | - name: this.profileConfigFormGroup.get('newDeviceProfileTitle').value, | |
250 | + name: this.deviceWizardFormGroup.get('newDeviceProfileTitle').value, | |
221 | 251 | type: DeviceProfileType.DEFAULT, |
222 | 252 | transportType: this.deviceWizardFormGroup.get('transportType').value, |
223 | 253 | profileData: { |
... | ... | @@ -229,11 +259,10 @@ export class DeviceWizardDialogComponent extends |
229 | 259 | return this.deviceProfileService.saveDeviceProfile(deviceProfile).pipe( |
230 | 260 | map(profile => profile.id), |
231 | 261 | tap((profileId) => { |
232 | - this.profileConfigFormGroup.patchValue({ | |
262 | + this.deviceWizardFormGroup.patchValue({ | |
233 | 263 | deviceProfileId: profileId, |
234 | 264 | addProfileType: 0 |
235 | 265 | }); |
236 | - this.addDeviceWizardStepper.selectedIndex = 2; | |
237 | 266 | }) |
238 | 267 | ); |
239 | 268 | } else { |
... | ... | @@ -241,7 +270,7 @@ export class DeviceWizardDialogComponent extends |
241 | 270 | } |
242 | 271 | } |
243 | 272 | |
244 | - private createdDevice(profileId: EntityId = this.profileConfigFormGroup.get('deviceProfileId').value): Observable<BaseData<HasId>> { | |
273 | + private createDevice(profileId: EntityId = this.deviceWizardFormGroup.get('deviceProfileId').value): Observable<BaseData<HasId>> { | |
245 | 274 | const device = { |
246 | 275 | name: this.deviceWizardFormGroup.get('name').value, |
247 | 276 | label: this.deviceWizardFormGroup.get('label').value, |
... | ... | @@ -252,21 +281,21 @@ export class DeviceWizardDialogComponent extends |
252 | 281 | }, |
253 | 282 | customerId: null |
254 | 283 | }; |
255 | - if (this.specificConfigFormGroup.get('customerId').value) { | |
284 | + if (this.customerFormGroup.get('customerId').value) { | |
256 | 285 | device.customerId = { |
257 | 286 | entityType: EntityType.CUSTOMER, |
258 | - id: this.specificConfigFormGroup.get('customerId').value | |
287 | + id: this.customerFormGroup.get('customerId').value | |
259 | 288 | }; |
260 | 289 | } |
261 | 290 | return this.data.entitiesTableConfig.saveEntity(device); |
262 | 291 | } |
263 | 292 | |
264 | - private saveCredential(device: BaseData<HasId>): Observable<boolean> { | |
265 | - if (this.specificConfigFormGroup.get('setCredential').value) { | |
293 | + private saveCredentials(device: BaseData<HasId>): Observable<boolean> { | |
294 | + if (this.credentialsFormGroup.get('setCredential').value) { | |
266 | 295 | return this.deviceService.getDeviceCredentials(device.id.id).pipe( |
267 | 296 | mergeMap( |
268 | 297 | (deviceCredentials) => { |
269 | - const deviceCredentialsValue = {...deviceCredentials, ...this.specificConfigFormGroup.value.credential}; | |
298 | + const deviceCredentialsValue = {...deviceCredentials, ...this.credentialsFormGroup.value.credential}; | |
270 | 299 | return this.deviceService.saveDeviceCredentials(deviceCredentialsValue); |
271 | 300 | } |
272 | 301 | ), |
... | ... | @@ -275,12 +304,28 @@ export class DeviceWizardDialogComponent extends |
275 | 304 | return of(true); |
276 | 305 | } |
277 | 306 | |
307 | + allValid(): boolean { | |
308 | + if (this.addDeviceWizardStepper.steps.find((item, index) => { | |
309 | + if (item.stepControl.invalid) { | |
310 | + item.interacted = true; | |
311 | + this.addDeviceWizardStepper.selectedIndex = index; | |
312 | + return true; | |
313 | + } else { | |
314 | + return false; | |
315 | + } | |
316 | + } )) { | |
317 | + return false; | |
318 | + } else { | |
319 | + return true; | |
320 | + } | |
321 | + } | |
322 | + | |
278 | 323 | changeStep($event: StepperSelectionEvent): void { |
279 | 324 | this.selectedIndex = $event.selectedIndex; |
280 | 325 | if (this.selectedIndex === this.maxStepperIndex) { |
281 | - this.nextStepButtonLabel$.next('action.add'); | |
326 | + this.showNext = false; | |
282 | 327 | } else { |
283 | - this.nextStepButtonLabel$.next('action.continue'); | |
328 | + this.showNext = true; | |
284 | 329 | } |
285 | 330 | } |
286 | 331 | } | ... | ... |
... | ... | @@ -296,7 +296,7 @@ export class DevicesTableConfigResolver implements Resolve<EntityTableConfig<Dev |
296 | 296 | name: this.translate.instant('device.add-device-text'), |
297 | 297 | icon: 'insert_drive_file', |
298 | 298 | isEnabled: () => true, |
299 | - onAction: ($event) => this.config.table.addEntity($event) | |
299 | + onAction: ($event) => this.deviceWizard($event) | |
300 | 300 | }, |
301 | 301 | { |
302 | 302 | name: this.translate.instant('device.import'), |
... | ... | @@ -304,12 +304,6 @@ export class DevicesTableConfigResolver implements Resolve<EntityTableConfig<Dev |
304 | 304 | isEnabled: () => true, |
305 | 305 | onAction: ($event) => this.importDevices($event) |
306 | 306 | }, |
307 | - { | |
308 | - name: this.translate.instant('device.wizard.device-wizard'), | |
309 | - icon: 'library_add', | |
310 | - isEnabled: () => true, | |
311 | - onAction: ($event) => this.deviceWizard($event) | |
312 | - }, | |
313 | 307 | ); |
314 | 308 | } |
315 | 309 | if (deviceScope === 'customer') { | ... | ... |
... | ... | @@ -72,6 +72,14 @@ export const deviceTransportTypeTranslationMap = new Map<DeviceTransportType, st |
72 | 72 | ] |
73 | 73 | ); |
74 | 74 | |
75 | +export const deviceTransportTypeHintMap = new Map<DeviceTransportType, string>( | |
76 | + [ | |
77 | + [DeviceTransportType.DEFAULT, 'device-profile.transport-type-default-hint'], | |
78 | + [DeviceTransportType.MQTT, 'device-profile.transport-type-mqtt-hint'], | |
79 | + [DeviceTransportType.LWM2M, 'device-profile.transport-type-lwm2m-hint'] | |
80 | + ] | |
81 | +); | |
82 | + | |
75 | 83 | export const mqttTransportPayloadTypeTranslationMap = new Map<MqttTransportPayloadType, string>( |
76 | 84 | [ |
77 | 85 | [MqttTransportPayloadType.JSON, 'device-profile.mqtt-device-payload-type-json'], | ... | ... |
... | ... | @@ -54,7 +54,8 @@ |
54 | 54 | "share-via": "Share via {{provider}}", |
55 | 55 | "continue": "Continue", |
56 | 56 | "discard-changes": "Discard Changes", |
57 | - "download": "Download" | |
57 | + "download": "Download", | |
58 | + "next-with-label": "Next: {{label}}" | |
58 | 59 | }, |
59 | 60 | "aggregation": { |
60 | 61 | "aggregation": "Aggregation", |
... | ... | @@ -760,8 +761,7 @@ |
760 | 761 | "wizard": { |
761 | 762 | "device-wizard": "Device Wizard", |
762 | 763 | "device-details": "Device details", |
763 | - "profile-configuration": "Profile configuration", | |
764 | - "new-device-profile": "New device profile", | |
764 | + "new-device-profile": "Create new device profile", | |
765 | 765 | "existing-device-profile": "Select existing device profile", |
766 | 766 | "specific-configuration": "Specific configuration", |
767 | 767 | "customer-to-assign-device": "Customer to assign the device", |
... | ... | @@ -784,6 +784,8 @@ |
784 | 784 | "set-default": "Make device profile default", |
785 | 785 | "delete": "Delete device profile", |
786 | 786 | "copyId": "Copy device profile Id", |
787 | + "new-device-profile-name": "Device profile name", | |
788 | + "new-device-profile-name-required": "Device profile name is required.", | |
787 | 789 | "name": "Name", |
788 | 790 | "name-required": "Name is required.", |
789 | 791 | "type": "Profile type", |
... | ... | @@ -792,8 +794,11 @@ |
792 | 794 | "transport-type": "Transport type", |
793 | 795 | "transport-type-required": "Transport type is required.", |
794 | 796 | "transport-type-default": "Default", |
797 | + "transport-type-default-hint": "Default transport type", | |
795 | 798 | "transport-type-mqtt": "MQTT", |
799 | + "transport-type-mqtt-hint": "MQTT transport type", | |
796 | 800 | "transport-type-lwm2m": "LWM2M", |
801 | + "transport-type-lwm2m-hint": "LWM2M transport type", | |
797 | 802 | "description": "Description", |
798 | 803 | "default": "Default", |
799 | 804 | "profile-configuration": "Profile configuration", |
... | ... | @@ -824,7 +829,8 @@ |
824 | 829 | "not-valid-multi-character": "Invalid use of a multi-level wildcard character", |
825 | 830 | "single-level-wildcards-hint": "<code>[+]</code> is suitable for any topic filter level. Ex.: <b>v1/devices/+/telemetry</b> or <b>+/devices/+/attributes</b>.", |
826 | 831 | "multi-level-wildcards-hint": "<code>[#]</code> can replace the topic filter itself and must be the last symbol of the topic. Ex.: <b>#</b> or <b>v1/devices/me/#</b>.", |
827 | - "alarm-rules": "Alarm rules ({{count}})", | |
832 | + "alarm-rules": "Alarm rules", | |
833 | + "alarm-rules-with-count": "Alarm rules ({{count}})", | |
828 | 834 | "no-alarm-rules": "No alarm rules configured", |
829 | 835 | "add-alarm-rule": "Add alarm rule", |
830 | 836 | "edit-alarm-rule": "Edit alarm rule", | ... | ... |