UI: Add mobile configure commands and add useSystemSettings in qr code widget

This commit is contained in:
Vladyslav_Prykhodko 2024-10-25 11:42:45 +03:00
parent b0e11e130a
commit c6396e4074
11 changed files with 232 additions and 29 deletions

View File

@ -35,6 +35,8 @@ public class QrCodeSettings extends BaseData<QrCodeSettingsId> implements HasTen
@Schema(description = "JSON object with Tenant Id.", accessMode = Schema.AccessMode.READ_ONLY)
private TenantId tenantId;
@Schema(description = "Use settings from system level", example = "true")
private boolean useSystemSettings;
@Schema(requiredMode = Schema.RequiredMode.REQUIRED, description = "Type of application: true means use default Thingsboard app", example = "true")
private boolean useDefaultApp;
@Schema(description = "Mobile app bundle.")

View File

@ -23,10 +23,53 @@
<mat-icon class="material-icons">close</mat-icon>
</button>
</mat-toolbar>
<div mat-dialog-content>
<div mat-dialog-content class="flex flex-col gap-4 xs:gap-2 gt-xs:max-h-[80vh]">
<div class="tb-form-panel stroked">
<div class="tb-form-panel-title" translate>mobile.configuration-step.prepare-environment-title</div>
<div class="flex gap-4">
<div class="flex-1" translate>mobile.configuration-step.prepare-environment-text</div>
<a mat-stroked-button
color="primary"
href="https://docs.flutter.dev/get-started/install"
target="_blank">
<mat-icon class="tb-mat-24">description</mat-icon>{{ 'common.documentation' | translate }}
</a>
</div>
</div>
<div class="tb-form-panel stroked">
<div class="tb-form-panel-title" translate>mobile.configuration-step.get-source-code-title</div>
<div translate>mobile.configuration-step.get-source-code-text</div>
<tb-markdown usePlainMarkdown containerClass="tb-command-code"
[data]=createMarkDownCommand(gitRepositoryLink)></tb-markdown>
</div>
<div class="tb-form-panel stroked">
<div class="tb-form-panel-title" translate>mobile.configuration-step.configure-api-title</div>
<div translate>mobile.configuration-step.configure-api-text</div>
<tb-markdown usePlainMarkdown containerClass="tb-command-code"
[data]=createMarkDownCommand(pathToConstants)></tb-markdown>
<div translate>mobile.configuration-step.configure-api-hint</div>
<tb-markdown usePlainMarkdown containerClass="tb-command-code"
[data]=createMarkDownCommand(configureApi)></tb-markdown>
</div>
<div class="tb-form-panel stroked">
<div class="tb-form-panel-title" translate>mobile.configuration-step.run-app-title</div>
<div translate>mobile.configuration-step.run-app-text</div>
<tb-markdown usePlainMarkdown containerClass="tb-command-code"
[data]=createMarkDownCommand(flutterRunCommand)></tb-markdown>
</div>
<div class="tb-form-panel stroked">
<div class="flex items-center gap-4">
<div class="flex-1" translate>mobile.configuration-step.more-information</div>
<a mat-stroked-button
color="primary"
href="https://docs.flutter.dev/get-started/install"
target="_blank">
<mat-icon class="tb-mat-24">rocket_launch</mat-icon>{{ 'mobile.configuration-step.getting-started' | translate }}
</a>
</div>
</div>
</div>
<div mat-dialog-actions class="tb-dialog-actions">
<div mat-dialog-actions class="tb-dialog-actions gap-2">
<mat-slide-toggle [class.!hidden]="!showDontShowAgain" [(ngModel)]="notShowAgain">{{ 'action.dont-show-again' | translate}}</mat-slide-toggle>
<span class="flex-1"></span>
<button mat-button

View File

@ -13,10 +13,84 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
@import "./../scss/constants";
:host {
height: 100%;
max-height: 100vh;
display: grid;
width: 800px;
width: 870px;
max-width: 100%;
grid-template-rows: min-content minmax(auto, 1fr) min-content;
}
:host-context(.mat-mdc-dialog-container) {
.tb-dialog-actions {
padding: 8px 16px 8px 24px;
}
}
:host ::ng-deep {
.tb-markdown-view {
.tb-command-code {
.code-wrapper {
padding: 0;
pre[class*=language-] {
margin: 0;
font-size: 14px;
background: #F3F6FA;
border-color: $tb-primary-color;
padding-right: 38px;
overflow: scroll;
padding-bottom: 4px;
min-height: 42px;
scrollbar-width: thin;
&::-webkit-scrollbar {
width: 4px;
height: 4px;
}
}
}
button.clipboard-btn {
right: -2px;
p {
color: $tb-primary-color;
}
p, div {
background-color: #F3F6FA;
}
div {
img {
display: none;
}
&:after {
content: "";
position: initial;
display: block;
width: 18px;
height: 18px;
background: $tb-primary-color;
mask-image: url(/assets/copy-code-icon.svg);
-webkit-mask-image: url(/assets/copy-code-icon.svg);
mask-repeat: no-repeat;
-webkit-mask-repeat: no-repeat;
}
}
}
}
}
.mdc-button__label > span {
.mat-icon {
vertical-align: text-bottom;
box-sizing: initial;
}
}
.tabs-icon {
margin-right: 8px;
}
.tb-form-panel.tb-tab-body {
padding: 16px 0 0;
}
}

View File

@ -20,9 +20,11 @@ import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state';
import { Router } from '@angular/router';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
import { ActionPreferencesPutUserSettings } from '@core/auth/auth.actions';
export interface MobileAppConfigurationDialogData {
afterAdd: boolean;
appSecret: string;
}
@Component({
@ -36,6 +38,12 @@ export class MobileAppConfigurationDialogComponent extends DialogComponent<Mobil
showDontShowAgain: boolean;
gitRepositoryLink = 'git clone -b master https://github.com/thingsboard/flutter_thingsboard_app.git';
pathToConstants = 'lib/constants/app_constants.dart';
flutterRunCommand = 'flutter run';
configureApi: string[] = [];
constructor(protected store: Store<AppState>,
protected router: Router,
@Inject(MAT_DIALOG_DATA) private data: MobileAppConfigurationDialogData,
@ -45,14 +53,33 @@ export class MobileAppConfigurationDialogComponent extends DialogComponent<Mobil
this.showDontShowAgain = this.data.afterAdd;
this.configureApi.push(`static final thingsBoardApiEndpoint = '${window.location.origin}';`);
this.configureApi.push(`static const thingsboardOAuth2AppSecret = '${this.data.appSecret}';`);
}
close(): void {
if (this.notShowAgain && this.showDontShowAgain) {
// this.store.dispatch(new ActionPreferencesPutUserSettings({ notDisplayConnectivityAfterAddDevice: true }));
this.store.dispatch(new ActionPreferencesPutUserSettings({ notDisplayConfigurationAfterAddMobileApp: true }));
this.dialogRef.close(null);
} else {
this.dialogRef.close(null);
}
}
createMarkDownCommand(commands: string | string[]): string {
if (Array.isArray(commands)) {
const formatCommands: Array<string> = [];
commands.forEach(command => formatCommands.push(this.createMarkDownSingleCommand(command)));
return formatCommands.join(`\n<br />\n\n`);
} else {
return this.createMarkDownSingleCommand(commands);
}
}
private createMarkDownSingleCommand(command: string): string {
return '```bash\n' +
command +
'{:copy-code}\n' +
'```';
}
}

View File

@ -52,6 +52,10 @@ import {
import {
MobileAppConfigurationDialogComponent, MobileAppConfigurationDialogData
} from '@home/pages/mobile/applications/mobile-app-configuration-dialog.component';
import { select, Store } from '@ngrx/store';
import { selectUserSettingsProperty } from '@core/auth/auth.selectors';
import { take } from 'rxjs/operators';
import { AppState } from '@core/core.state';
@Injectable()
export class MobileAppTableConfigResolver {
@ -62,7 +66,9 @@ export class MobileAppTableConfigResolver {
private datePipe: DatePipe,
private mobileAppService: MobileAppService,
private truncatePipe: TruncatePipe,
private dialog: MatDialog,) {
private dialog: MatDialog,
private store: Store<AppState>,
) {
this.config.selectionEnabled = false;
this.config.entityType = EntityType.MOBILE_APP;
this.config.addEnabled = false;
@ -124,6 +130,18 @@ export class MobileAppTableConfigResolver {
this.config.saveEntity = (mobileApp) => this.mobileAppService.saveMobileApp(mobileApp);
this.config.deleteEntity = id => this.mobileAppService.deleteMobileApp(id.id);
this.config.entityAdded = (mobileApp) => {
this.store.pipe(select(selectUserSettingsProperty( 'notDisplayConfigurationAfterAddMobileApp'))).pipe(
take(1)
).subscribe((settings: boolean) => {
if(!settings) {
this.configurationApp(null, mobileApp, true);
} else {
this.config.updateData();
}
});
}
this.config.cellActionDescriptors = this.configureCellActions();
}
@ -176,7 +194,8 @@ export class MobileAppTableConfigResolver {
disableClose: true,
panelClass: ['tb-dialog', 'tb-fullscreen-dialog'],
data: {
afterAdd
afterAdd,
appSecret: entity.appSecret
}
}).afterClosed()
.subscribe(() => {

View File

@ -26,7 +26,12 @@
<div style="height: 4px;" *ngIf="!(isLoading$ | async)"></div>
<mat-card-content style="padding-top: 16px">
<div class="tb-form-panel no-border no-padding" [formGroup]="mobileAppSettingsForm">
<div class="tb-form-panel">
<div class="tb-form-row no-border no-padding-bottom" *ngIf="isTenantAdmin()">
<mat-slide-toggle class="mat-slide" formControlName="useSystemSettings">
{{ 'admin.mobile-app.use-system-settings' | translate }}
</mat-slide-toggle>
</div>
<div class="tb-form-panel" *ngIf="!mobileAppSettingsForm.get('useSystemSettings').value">
<div class="tb-form-row column-xs no-border no-padding space-between">
<div class="tb-form-panel-title" translate>admin.mobile-app.applications</div>
<tb-toggle-select formControlName="useDefaultApp">
@ -151,7 +156,7 @@
<!-- </div>-->
<!-- </div>-->
</div>
<div class="tb-form-panel" formGroupName="qrCodeConfig">
<div class="tb-form-panel" formGroupName="qrCodeConfig" *ngIf="!mobileAppSettingsForm.get('useSystemSettings').value">
<div class="tb-form-row column-xs no-border no-padding space-between">
<div class="tb-form-panel-title" translate>admin.mobile-app.appearance-on-home-page</div>
<tb-toggle-select formControlName="showOnHomePage">

View File

@ -14,28 +14,26 @@
/// limitations under the License.
///
import { Component, OnDestroy } from '@angular/core';
import { Component } from '@angular/core';
import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state';
import { PageComponent } from '@shared/components/page.component';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { HasConfirmForm } from '@core/guards/confirm-on-exit.guard';
import { Subject, takeUntil } from 'rxjs';
import { MobileApplicationService } from '@core/http/mobile-application.service';
import {
BadgePosition,
badgePositionTranslationsMap,
QrCodeSettings
} from '@shared/models/mobile-app.models';
import { BadgePosition, badgePositionTranslationsMap, QrCodeSettings } from '@shared/models/mobile-app.models';
import { ActionUpdateMobileQrCodeEnabled } from '@core/auth/auth.actions';
import { EntityType } from '@shared/models/entity-type.models';
import { getCurrentAuthUser } from '@core/auth/auth.selectors';
import { Authority } from '@shared/models/authority.enum';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
@Component({
selector: 'tb-mobile-qr-code-widget',
templateUrl: './mobile-qr-code-widget-settings.component.html',
styleUrls: ['mobile-qr-code-widget-settings.component.scss', '../../admin/settings-card.scss']
})
export class MobileQrCodeWidgetSettingsComponent extends PageComponent implements HasConfirmForm, OnDestroy {
export class MobileQrCodeWidgetSettingsComponent extends PageComponent implements HasConfirmForm {
mobileAppSettingsForm: FormGroup;
mobileAppSettings: QrCodeSettings;
@ -43,7 +41,7 @@ export class MobileQrCodeWidgetSettingsComponent extends PageComponent implement
readonly badgePositionTranslationsMap = badgePositionTranslationsMap;
readonly entityType = EntityType;
private readonly destroy$ = new Subject<void>();
private authUser = getCurrentAuthUser(this.store);
constructor(protected store: Store<AppState>,
private mobileAppService: MobileApplicationService,
@ -52,8 +50,23 @@ export class MobileQrCodeWidgetSettingsComponent extends PageComponent implement
this.buildMobileAppSettingsForm();
this.mobileAppService.getMobileAppSettings()
.subscribe(settings => this.processMobileAppSettings(settings));
if (this.isTenantAdmin()) {
// this.mobileAppSettingsForm.get('useSystemSettings').valueChanges.pipe(
// takeUntilDestroyed()
// ).subscribe(value => {
// if (value) {
// this.mobileAppSettingsForm.get('androidConfig.enabled').disable();
// this.mobileAppSettingsForm.get('iosConfig.enabled').disable();
// this.mobileAppSettingsForm.get('qrCodeConfig.qrCodeLabelEnabled').disable();
// } else {
// this.mobileAppSettingsForm.get('androidConfig.enabled').enable();
// this.mobileAppSettingsForm.get('iosConfig.enabled').enable();
// this.mobileAppSettingsForm.get('qrCodeConfig.qrCodeLabelEnabled').enable();
// }
// });
}
this.mobileAppSettingsForm.get('useDefaultApp').valueChanges.pipe(
takeUntil(this.destroy$)
takeUntilDestroyed()
).subscribe(value => {
if (value) {
this.mobileAppSettingsForm.get('mobileAppBundleId').disable({emitEvent: false});
@ -77,17 +90,17 @@ export class MobileQrCodeWidgetSettingsComponent extends PageComponent implement
}
});
// this.mobileAppSettingsForm.get('androidConfig.enabled').valueChanges.pipe(
// takeUntil(this.destroy$)
// takeUntilDestroyed()
// ).subscribe(value => {
// this.androidEnableChanged(value);
// });
// this.mobileAppSettingsForm.get('iosConfig.enabled').valueChanges.pipe(
// takeUntil(this.destroy$)
// takeUntilDestroyed()
// ).subscribe(value => {
// this.iosEnableChanged(value);
// });
this.mobileAppSettingsForm.get('qrCodeConfig.showOnHomePage').valueChanges.pipe(
takeUntil(this.destroy$)
takeUntilDestroyed()
).subscribe(value => {
if (value) {
this.mobileAppSettingsForm.get('qrCodeConfig').enable({emitEvent: false});
@ -99,7 +112,7 @@ export class MobileQrCodeWidgetSettingsComponent extends PageComponent implement
this.mobileAppSettingsForm.get('qrCodeConfig.qrCodeLabelEnabled').updateValueAndValidity({onlySelf: true});
});
this.mobileAppSettingsForm.get('qrCodeConfig.badgeEnabled').valueChanges.pipe(
takeUntil(this.destroy$)
takeUntilDestroyed()
).subscribe(value => {
if (value) {
// if (this.mobileAppSettingsForm.get('androidConfig.enabled').value || this.mobileAppSettingsForm.get('iosConfig.enabled').value) {
@ -114,7 +127,7 @@ export class MobileQrCodeWidgetSettingsComponent extends PageComponent implement
}
});
this.mobileAppSettingsForm.get('qrCodeConfig.qrCodeLabelEnabled').valueChanges.pipe(
takeUntil(this.destroy$)
takeUntilDestroyed()
).subscribe(value => {
if (value && this.mobileAppSettingsForm.get('qrCodeConfig.showOnHomePage').value) {
this.mobileAppSettingsForm.get('qrCodeConfig.qrCodeLabel').enable({emitEvent: false});
@ -124,14 +137,13 @@ export class MobileQrCodeWidgetSettingsComponent extends PageComponent implement
});
}
ngOnDestroy() {
super.ngOnDestroy();
this.destroy$.next();
this.destroy$.complete();
public isTenantAdmin(): boolean {
return this.authUser.authority === Authority.TENANT_ADMIN;
}
private buildMobileAppSettingsForm() {
this.mobileAppSettingsForm = this.fb.group({
useSystemSettings: [false],
useDefaultApp: [true],
mobileAppBundleId: [{value: null, disabled: true}, Validators.required],
// androidConfig: this.fb.group({
@ -157,6 +169,9 @@ export class MobileQrCodeWidgetSettingsComponent extends PageComponent implement
private processMobileAppSettings(mobileAppSettings: QrCodeSettings): void {
this.mobileAppSettings = {...mobileAppSettings};
if (!this.isTenantAdmin()) {
this.mobileAppSettings.useSystemSettings = false;
}
this.mobileAppSettingsForm.reset(this.mobileAppSettings);
}

View File

@ -22,6 +22,7 @@ import { MobileAppBundleId } from '@shared/models/id/mobile-app-bundle-id';
import { deepClone, isNotEmptyStr } from '@core/utils';
export interface QrCodeSettings extends HasTenantId {
useSystemSettings: boolean;
useDefaultApp: boolean;
mobileAppBundleId: MobileAppBundleId
androidConfig: any; //TODO: need remove

View File

@ -18,6 +18,7 @@ export interface UserSettings {
openedMenuSections?: string[];
notDisplayConnectivityAfterAddDevice?: boolean;
notDisplayInstructionsAfterAddEdge?: boolean;
notDisplayConfigurationAfterAddMobileApp?: boolean;
includeBundleWidgetsInExport?: boolean;
}

View File

@ -3489,7 +3489,20 @@
"qr-code-widget": "QR code widget",
"type-here": "Type hero",
"configuration-dialog": "Configuration dialog",
"configuration-app": "Configuration app"
"configuration-app": "Configuration app",
"configuration-step": {
"prepare-environment-title": "Prepare development environment",
"prepare-environment-text": "Flutter ThingsBoard Mobile Application requires Flutter SDK. Follow instructions to set up Flutter SDK.",
"get-source-code-title": "Get app source code",
"get-source-code-text": "You can get Flutter ThingsBoard Mobile Application source code by cloning it from the GitHub repository:",
"configure-api-title": "Configure ThingsBoard API endpoint",
"configure-api-text": "Open the flutter_thingsboard_app project in your editor/IDE. Edit:",
"configure-api-hint": "Set the value of the thingsBoardApiEndpoint constant to match the API endpoint of your ThingsBoard server instance. Do not use “localhost” or “127.0.0.1” hostnames.",
"run-app-title": "Run the app",
"run-app-text": "Run the app as described in your IDE.\nIf using the terminal, run the app with the following command:",
"more-information": "Detailed information may be found in our Getting Started documentation.",
"getting-started": "Getting Started"
}
},
"notification": {
"action-button": "Action button",

View File

@ -907,6 +907,9 @@ pre.tb-highlight {
&.tb-mat-20 {
@include tb-mat-icon-size(20);
}
&.tb-mat-24 {
@include tb-mat-icon-size(24);
}
&.tb-mat-28 {
@include tb-mat-icon-size(28);
}