From c7b57ca543f36e4691efae2e01d8e262afc04426 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Fri, 9 Oct 2020 17:19:51 +0300 Subject: [PATCH] Add Device Wizard improvements --- .../add-device-profile-dialog.component.html | 2 +- .../device-profile-autocomplete.component.ts | 19 +- .../device-profile-data.component.html | 2 +- .../device-wizard-dialog.component.html | 126 +++++++------ .../device-wizard-dialog.component.scss | 46 ++++- .../wizard/device-wizard-dialog.component.ts | 177 +++++++++++------- .../device/devices-table-config.resolver.ts | 8 +- ui-ngx/src/app/shared/models/device.models.ts | 8 + .../assets/locale/locale.constant-en_US.json | 14 +- 9 files changed, 253 insertions(+), 149 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/profile/add-device-profile-dialog.component.html b/ui-ngx/src/app/modules/home/components/profile/add-device-profile-dialog.component.html index d7f119a3b7..df70ea4e8f 100644 --- a/ui-ngx/src/app/modules/home/components/profile/add-device-profile-dialog.component.html +++ b/ui-ngx/src/app/modules/home/components/profile/add-device-profile-dialog.component.html @@ -85,7 +85,7 @@
- {{'device-profile.alarm-rules' | translate: + {{'device-profile.alarm-rules-with-count' | translate: {count: alarmRulesFormGroup.get('alarms').value ? alarmRulesFormGroup.get('alarms').value.length : 0} }} { - if (profile) { + if (profile && !this.transportType || (profile.transportType === this.transportType)) { this.selectDeviceProfileFormGroup.get('deviceProfile').patchValue(profile, {emitEvent: false}); this.updateView(profile); } diff --git a/ui-ngx/src/app/modules/home/components/profile/device-profile-data.component.html b/ui-ngx/src/app/modules/home/components/profile/device-profile-data.component.html index daae838285..2cc3ccd9e2 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device-profile-data.component.html +++ b/ui-ngx/src/app/modules/home/components/profile/device-profile-data.component.html @@ -42,7 +42,7 @@ -
{{'device-profile.alarm-rules' | translate: +
{{'device-profile.alarm-rules-with-count' | translate: {count: deviceProfileDataFormGroup.get('alarms').value ? deviceProfileDataFormGroup.get('alarms').value.length : 0} }}
diff --git a/ui-ngx/src/app/modules/home/components/wizard/device-wizard-dialog.component.html b/ui-ngx/src/app/modules/home/components/wizard/device-wizard-dialog.component.html index c391bc848a..6cf1e687e3 100644 --- a/ui-ngx/src/app/modules/home/components/wizard/device-wizard-dialog.component.html +++ b/ui-ngx/src/app/modules/home/components/wizard/device-wizard-dialog.component.html @@ -15,7 +15,7 @@ limitations under the License. --> -
+

device.add-device-text

@@ -29,7 +29,7 @@
- + check @@ -48,17 +48,49 @@ device.label - + device-profile.transport-type {{deviceTransportTypeTranslations.get(type) | translate}} + + {{deviceTransportTypeHints.get(deviceWizardFormGroup.get('transportType').value) | translate}} + {{ 'device-profile.transport-type-required' | translate }} +
+ + + device.wizard.existing-device-profile + + + device.wizard.new-device-profile + + +
+ + + + device-profile.new-device-profile-name + + + {{ 'device-profile.new-device-profile-name-required' | translate }} + + +
+
{{ 'device.is-gateway' | translate }} @@ -69,39 +101,7 @@ - -
- {{ 'device.wizard.profile-configuration' | translate}} - - -
- device.wizard.existing-device-profile - - -
-
- -
- device.wizard.new-device-profile - - device-profile.device-profile - - - {{ 'device-profile.device-profile-required' | translate }} - - -
-
-
-
-
- +
{{ 'device-profile.transport-configuration' | translate }}
- +
- {{'device-profile.alarm-rules' | translate: + {{'device-profile.alarm-rules-with-count' | translate: {count: alarmRulesFormGroup.get('alarms').value ? alarmRulesFormGroup.get('alarms').value.length : 0} }}
- - {{ 'device.wizard.specific-configuration' | translate }} -
+ + {{ 'device.credentials' | translate }} + + {{ 'device.wizard.add-credential' | translate }} + + + +
+ + {{ 'customer.customer' | translate }} +
- {{ 'device.wizard.add-credential' | translate }} - -
-
- - -
+
+
+ +
+
- + +
+ + +
diff --git a/ui-ngx/src/app/modules/home/components/wizard/device-wizard-dialog.component.scss b/ui-ngx/src/app/modules/home/components/wizard/device-wizard-dialog.component.scss index cafcce74b6..1426e0f201 100644 --- a/ui-ngx/src/app/modules/home/components/wizard/device-wizard-dialog.component.scss +++ b/ui-ngx/src/app/modules/home/components/wizard/device-wizard-dialog.component.scss @@ -13,25 +13,51 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -:host { - .mat-dialog-content { - display: flex; - flex-direction: column; - overflow: hidden; - .mat-stepper-horizontal { - display: flex; - flex-direction: column; - overflow: hidden; +@import "../../../../../scss/constants"; + +:host-context(.tb-fullscreen-dialog .mat-dialog-container) { + @media #{$mat-lt-sm} { + .mat-dialog-content { + max-height: 75vh; } } } :host ::ng-deep { .mat-dialog-content { + display: flex; + flex-direction: column; + height: 100%; + .mat-stepper-horizontal { + display: flex; + flex-direction: column; + height: 100%; + overflow: hidden; + @media #{$mat-lt-sm} { + .mat-step-label { + white-space: normal; + overflow: visible; + .mat-step-text-label { + overflow: visible; + } + } + } .mat-horizontal-content-container { - overflow: auto; + height: 450px; + max-height: 100%; + width: 100%;; + overflow-y: auto; + @media #{$mat-gt-sm} { + min-width: 800px; + } + } + .mat-horizontal-stepper-content[aria-expanded=true] { + height: 100%; + form { + height: 100%; + } } } } diff --git a/ui-ngx/src/app/modules/home/components/wizard/device-wizard-dialog.component.ts b/ui-ngx/src/app/modules/home/components/wizard/device-wizard-dialog.component.ts index 15efa868b2..9def61c4b6 100644 --- a/ui-ngx/src/app/modules/home/components/wizard/device-wizard-dialog.component.ts +++ b/ui-ngx/src/app/modules/home/components/wizard/device-wizard-dialog.component.ts @@ -26,7 +26,7 @@ import { createDeviceProfileTransportConfiguration, DeviceProfile, DeviceProfileType, - DeviceTransportType, + DeviceTransportType, deviceTransportTypeHintMap, deviceTransportTypeTranslationMap } from '@shared/models/device.models'; import { MatHorizontalStepper } from '@angular/material/stepper'; @@ -35,11 +35,13 @@ import { BaseData, HasId } from '@shared/models/base-data'; import { EntityType } from '@shared/models/entity-type.models'; import { DeviceProfileService } from '@core/http/device-profile.service'; import { EntityId } from '@shared/models/id/entity-id'; -import { BehaviorSubject, Observable, of, Subscription } from 'rxjs'; +import { Observable, of, Subscription } from 'rxjs'; import { map, mergeMap, tap } from 'rxjs/operators'; import { DeviceService } from '@core/http/device.service'; import { ErrorStateMatcher } from '@angular/material/core'; import { StepperSelectionEvent } from '@angular/cdk/stepper'; +import { BreakpointObserver, BreakpointState } from '@angular/cdk/layout'; +import { MediaBreakpoints } from '@shared/models/constants'; @Component({ selector: 'tb-device-wizard', @@ -54,9 +56,10 @@ export class DeviceWizardDialogComponent extends selectedIndex = 0; - nextStepButtonLabel$ = new BehaviorSubject('action.continue'); + showNext = true; - createdProfile = false; + createProfile = false; + createTransportConfiguration = false; entityType = EntityType; @@ -64,15 +67,19 @@ export class DeviceWizardDialogComponent extends deviceTransportTypeTranslations = deviceTransportTypeTranslationMap; - deviceWizardFormGroup: FormGroup; + deviceTransportTypeHints = deviceTransportTypeHintMap; - profileConfigFormGroup: FormGroup; + deviceWizardFormGroup: FormGroup; transportConfigFormGroup: FormGroup; alarmRulesFormGroup: FormGroup; - specificConfigFormGroup: FormGroup; + credentialsFormGroup: FormGroup; + + customerFormGroup: FormGroup; + + labelPosition = 'end'; private subscriptions: Subscription[] = []; @@ -83,6 +90,7 @@ export class DeviceWizardDialogComponent extends public dialogRef: MatDialogRef, private deviceProfileService: DeviceProfileService, private deviceService: DeviceService, + private breakpointObserver: BreakpointObserver, private fb: FormBuilder) { super(store, router, dialogRef); this.deviceWizardFormGroup = this.fb.group({ @@ -90,33 +98,32 @@ export class DeviceWizardDialogComponent extends label: [''], gateway: [false], transportType: [DeviceTransportType.DEFAULT, Validators.required], + addProfileType: [0], + deviceProfileId: [null, Validators.required], + newDeviceProfileTitle: [{value: null, disabled: true}], description: [''] } ); - this.profileConfigFormGroup = this.fb.group({ - addProfileType: [0], - deviceProfileId: [null, Validators.required], - newDeviceProfileTitle: [{value: null, disabled: true}] - } - ); - - this.subscriptions.push(this.profileConfigFormGroup.get('addProfileType').valueChanges.subscribe( + this.subscriptions.push(this.deviceWizardFormGroup.get('addProfileType').valueChanges.subscribe( (addProfileType: number) => { if (addProfileType === 0) { - this.profileConfigFormGroup.get('deviceProfileId').setValidators([Validators.required]); - this.profileConfigFormGroup.get('deviceProfileId').enable(); - this.profileConfigFormGroup.get('newDeviceProfileTitle').setValidators(null); - this.profileConfigFormGroup.get('newDeviceProfileTitle').disable(); - this.profileConfigFormGroup.updateValueAndValidity(); - this.createdProfile = false; + this.deviceWizardFormGroup.get('deviceProfileId').setValidators([Validators.required]); + this.deviceWizardFormGroup.get('deviceProfileId').enable(); + this.deviceWizardFormGroup.get('newDeviceProfileTitle').setValidators(null); + this.deviceWizardFormGroup.get('newDeviceProfileTitle').disable(); + this.deviceWizardFormGroup.updateValueAndValidity(); + this.createProfile = false; + this.createTransportConfiguration = false; } else { - this.profileConfigFormGroup.get('deviceProfileId').setValidators(null); - this.profileConfigFormGroup.get('deviceProfileId').disable(); - this.profileConfigFormGroup.get('newDeviceProfileTitle').setValidators([Validators.required]); - this.profileConfigFormGroup.get('newDeviceProfileTitle').enable(); - this.profileConfigFormGroup.updateValueAndValidity(); - this.createdProfile = true; + this.deviceWizardFormGroup.get('deviceProfileId').setValidators(null); + this.deviceWizardFormGroup.get('deviceProfileId').disable(); + this.deviceWizardFormGroup.get('newDeviceProfileTitle').setValidators([Validators.required]); + this.deviceWizardFormGroup.get('newDeviceProfileTitle').enable(); + this.deviceWizardFormGroup.updateValueAndValidity(); + this.createProfile = true; + this.createTransportConfiguration = this.deviceWizardFormGroup.get('transportType').value && + DeviceTransportType.DEFAULT !== this.deviceWizardFormGroup.get('transportType').value; } } )); @@ -135,20 +142,37 @@ export class DeviceWizardDialogComponent extends } ); - this.specificConfigFormGroup = this.fb.group({ - customerId: [null], + this.credentialsFormGroup = this.fb.group({ setCredential: [false], credential: [{value: null, disabled: true}] } ); - this.subscriptions.push(this.specificConfigFormGroup.get('setCredential').valueChanges.subscribe((value) => { + this.subscriptions.push(this.credentialsFormGroup.get('setCredential').valueChanges.subscribe((value) => { if (value) { - this.specificConfigFormGroup.get('credential').enable(); + this.credentialsFormGroup.get('credential').enable(); } else { - this.specificConfigFormGroup.get('credential').disable(); + this.credentialsFormGroup.get('credential').disable(); } })); + + this.customerFormGroup = this.fb.group({ + customerId: [null] + } + ); + + this.labelPosition = this.breakpointObserver.isMatched(MediaBreakpoints['gt-sm']) ? 'end' : 'bottom'; + + this.subscriptions.push(this.breakpointObserver + .observe(MediaBreakpoints['gt-sm']) + .subscribe((state: BreakpointState) => { + if (state.matches) { + this.labelPosition = 'end'; + } else { + this.labelPosition = 'bottom'; + } + } + )); } ngOnDestroy() { @@ -171,26 +195,28 @@ export class DeviceWizardDialogComponent extends } nextStep(): void { - if (this.selectedIndex < this.maxStepperIndex) { - this.addDeviceWizardStepper.next(); - } else { - this.add(); - } + this.addDeviceWizardStepper.next(); } - get selectedForm(): FormGroup { - const index = !this.createdProfile && this.selectedIndex === this.maxStepperIndex ? 4 : this.selectedIndex; + getFormLabel(index: number): string { + if (index > 0) { + if (!this.createProfile) { + index += 2; + } else if (!this.createTransportConfiguration) { + index += 1; + } + } switch (index) { case 0: - return this.deviceWizardFormGroup; + return 'device.wizard.device-details'; case 1: - return this.profileConfigFormGroup; + return 'device-profile.transport-configuration'; case 2: - return this.transportConfigFormGroup; + return 'device-profile.alarm-rules'; case 3: - return this.alarmRulesFormGroup; + return 'device.credentials'; case 4: - return this.specificConfigFormGroup; + return 'customer.customer'; } } @@ -201,23 +227,27 @@ export class DeviceWizardDialogComponent extends private deviceProfileTransportTypeChanged(deviceTransportType: DeviceTransportType): void { this.transportConfigFormGroup.patchValue( {transportConfiguration: createDeviceProfileTransportConfiguration(deviceTransportType)}); + this.createTransportConfiguration = this.createProfile && deviceTransportType && + DeviceTransportType.DEFAULT !== deviceTransportType; } - private add(): void { - this.creatProfile().pipe( - mergeMap(profileId => this.createdDevice(profileId)), - mergeMap(device => this.saveCredential(device)) - ).subscribe( - (created) => { - this.dialogRef.close(created); - } - ); + add(): void { + if (this.allValid()) { + this.createDeviceProfile().pipe( + mergeMap(profileId => this.createDevice(profileId)), + mergeMap(device => this.saveCredentials(device)) + ).subscribe( + (created) => { + this.dialogRef.close(created); + } + ); + } } - private creatProfile(): Observable { - if (this.profileConfigFormGroup.get('addProfileType').value) { + private createDeviceProfile(): Observable { + if (this.deviceWizardFormGroup.get('addProfileType').value) { const deviceProfile: DeviceProfile = { - name: this.profileConfigFormGroup.get('newDeviceProfileTitle').value, + name: this.deviceWizardFormGroup.get('newDeviceProfileTitle').value, type: DeviceProfileType.DEFAULT, transportType: this.deviceWizardFormGroup.get('transportType').value, profileData: { @@ -229,11 +259,10 @@ export class DeviceWizardDialogComponent extends return this.deviceProfileService.saveDeviceProfile(deviceProfile).pipe( map(profile => profile.id), tap((profileId) => { - this.profileConfigFormGroup.patchValue({ + this.deviceWizardFormGroup.patchValue({ deviceProfileId: profileId, addProfileType: 0 }); - this.addDeviceWizardStepper.selectedIndex = 2; }) ); } else { @@ -241,7 +270,7 @@ export class DeviceWizardDialogComponent extends } } - private createdDevice(profileId: EntityId = this.profileConfigFormGroup.get('deviceProfileId').value): Observable> { + private createDevice(profileId: EntityId = this.deviceWizardFormGroup.get('deviceProfileId').value): Observable> { const device = { name: this.deviceWizardFormGroup.get('name').value, label: this.deviceWizardFormGroup.get('label').value, @@ -252,21 +281,21 @@ export class DeviceWizardDialogComponent extends }, customerId: null }; - if (this.specificConfigFormGroup.get('customerId').value) { + if (this.customerFormGroup.get('customerId').value) { device.customerId = { entityType: EntityType.CUSTOMER, - id: this.specificConfigFormGroup.get('customerId').value + id: this.customerFormGroup.get('customerId').value }; } return this.data.entitiesTableConfig.saveEntity(device); } - private saveCredential(device: BaseData): Observable { - if (this.specificConfigFormGroup.get('setCredential').value) { + private saveCredentials(device: BaseData): Observable { + if (this.credentialsFormGroup.get('setCredential').value) { return this.deviceService.getDeviceCredentials(device.id.id).pipe( mergeMap( (deviceCredentials) => { - const deviceCredentialsValue = {...deviceCredentials, ...this.specificConfigFormGroup.value.credential}; + const deviceCredentialsValue = {...deviceCredentials, ...this.credentialsFormGroup.value.credential}; return this.deviceService.saveDeviceCredentials(deviceCredentialsValue); } ), @@ -275,12 +304,28 @@ export class DeviceWizardDialogComponent extends return of(true); } + allValid(): boolean { + if (this.addDeviceWizardStepper.steps.find((item, index) => { + if (item.stepControl.invalid) { + item.interacted = true; + this.addDeviceWizardStepper.selectedIndex = index; + return true; + } else { + return false; + } + } )) { + return false; + } else { + return true; + } + } + changeStep($event: StepperSelectionEvent): void { this.selectedIndex = $event.selectedIndex; if (this.selectedIndex === this.maxStepperIndex) { - this.nextStepButtonLabel$.next('action.add'); + this.showNext = false; } else { - this.nextStepButtonLabel$.next('action.continue'); + this.showNext = true; } } } diff --git a/ui-ngx/src/app/modules/home/pages/device/devices-table-config.resolver.ts b/ui-ngx/src/app/modules/home/pages/device/devices-table-config.resolver.ts index 3a7a7f9027..0aaf8e62e7 100644 --- a/ui-ngx/src/app/modules/home/pages/device/devices-table-config.resolver.ts +++ b/ui-ngx/src/app/modules/home/pages/device/devices-table-config.resolver.ts @@ -296,7 +296,7 @@ export class DevicesTableConfigResolver implements Resolve true, - onAction: ($event) => this.config.table.addEntity($event) + onAction: ($event) => this.deviceWizard($event) }, { name: this.translate.instant('device.import'), @@ -304,12 +304,6 @@ export class DevicesTableConfigResolver implements Resolve true, onAction: ($event) => this.importDevices($event) }, - { - name: this.translate.instant('device.wizard.device-wizard'), - icon: 'library_add', - isEnabled: () => true, - onAction: ($event) => this.deviceWizard($event) - }, ); } if (deviceScope === 'customer') { diff --git a/ui-ngx/src/app/shared/models/device.models.ts b/ui-ngx/src/app/shared/models/device.models.ts index addb726ed5..0bbc93eb12 100644 --- a/ui-ngx/src/app/shared/models/device.models.ts +++ b/ui-ngx/src/app/shared/models/device.models.ts @@ -72,6 +72,14 @@ export const deviceTransportTypeTranslationMap = new Map( + [ + [DeviceTransportType.DEFAULT, 'device-profile.transport-type-default-hint'], + [DeviceTransportType.MQTT, 'device-profile.transport-type-mqtt-hint'], + [DeviceTransportType.LWM2M, 'device-profile.transport-type-lwm2m-hint'] + ] +); + export const mqttTransportPayloadTypeTranslationMap = new Map( [ [MqttTransportPayloadType.JSON, 'device-profile.mqtt-device-payload-type-json'], diff --git a/ui-ngx/src/assets/locale/locale.constant-en_US.json b/ui-ngx/src/assets/locale/locale.constant-en_US.json index 2a9edbb6e4..08aa57b61a 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -54,7 +54,8 @@ "share-via": "Share via {{provider}}", "continue": "Continue", "discard-changes": "Discard Changes", - "download": "Download" + "download": "Download", + "next-with-label": "Next: {{label}}" }, "aggregation": { "aggregation": "Aggregation", @@ -760,8 +761,7 @@ "wizard": { "device-wizard": "Device Wizard", "device-details": "Device details", - "profile-configuration": "Profile configuration", - "new-device-profile": "New device profile", + "new-device-profile": "Create new device profile", "existing-device-profile": "Select existing device profile", "specific-configuration": "Specific configuration", "customer-to-assign-device": "Customer to assign the device", @@ -784,6 +784,8 @@ "set-default": "Make device profile default", "delete": "Delete device profile", "copyId": "Copy device profile Id", + "new-device-profile-name": "Device profile name", + "new-device-profile-name-required": "Device profile name is required.", "name": "Name", "name-required": "Name is required.", "type": "Profile type", @@ -792,8 +794,11 @@ "transport-type": "Transport type", "transport-type-required": "Transport type is required.", "transport-type-default": "Default", + "transport-type-default-hint": "Default transport type", "transport-type-mqtt": "MQTT", + "transport-type-mqtt-hint": "MQTT transport type", "transport-type-lwm2m": "LWM2M", + "transport-type-lwm2m-hint": "LWM2M transport type", "description": "Description", "default": "Default", "profile-configuration": "Profile configuration", @@ -824,7 +829,8 @@ "not-valid-multi-character": "Invalid use of a multi-level wildcard character", "single-level-wildcards-hint": "[+] is suitable for any topic filter level. Ex.: v1/devices/+/telemetry or +/devices/+/attributes.", "multi-level-wildcards-hint": "[#] can replace the topic filter itself and must be the last symbol of the topic. Ex.: # or v1/devices/me/#.", - "alarm-rules": "Alarm rules ({{count}})", + "alarm-rules": "Alarm rules", + "alarm-rules-with-count": "Alarm rules ({{count}})", "no-alarm-rules": "No alarm rules configured", "add-alarm-rule": "Add alarm rule", "edit-alarm-rule": "Edit alarm rule",