Merge pull request #13679 from vvlladd28/improvement/mobile-center/configure-dialog
Improved mobile configuration dialog: replace manual setup with JSON config file
This commit is contained in:
commit
78a374a5ca
@ -42,3 +42,5 @@ DROP INDEX IF EXISTS idx_customer_external_id;
|
|||||||
DROP INDEX IF EXISTS idx_widgets_bundle_external_id;
|
DROP INDEX IF EXISTS idx_widgets_bundle_external_id;
|
||||||
|
|
||||||
-- DROP INDEXES THAT DUPLICATE UNIQUE CONSTRAINT END
|
-- DROP INDEXES THAT DUPLICATE UNIQUE CONSTRAINT END
|
||||||
|
|
||||||
|
ALTER TABLE mobile_app ADD COLUMN IF NOT EXISTS title varchar(255);
|
||||||
@ -43,6 +43,9 @@ public class MobileApp extends BaseData<MobileAppId> implements HasTenantId, Has
|
|||||||
@NotBlank
|
@NotBlank
|
||||||
@Length(fieldName = "pkgName")
|
@Length(fieldName = "pkgName")
|
||||||
private String pkgName;
|
private String pkgName;
|
||||||
|
@Schema(description = "Application title")
|
||||||
|
@Length(fieldName = "title")
|
||||||
|
private String title;
|
||||||
@Schema(description = "Application secret. The length must be at least 16 characters", requiredMode = Schema.RequiredMode.REQUIRED)
|
@Schema(description = "Application secret. The length must be at least 16 characters", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||||
@NotEmpty
|
@NotEmpty
|
||||||
@Length(fieldName = "appSecret", min = 16, max = 2048, message = "must be at least 16 and max 2048 characters")
|
@Length(fieldName = "appSecret", min = 16, max = 2048, message = "must be at least 16 and max 2048 characters")
|
||||||
@ -72,6 +75,7 @@ public class MobileApp extends BaseData<MobileAppId> implements HasTenantId, Has
|
|||||||
super(mobile);
|
super(mobile);
|
||||||
this.tenantId = mobile.tenantId;
|
this.tenantId = mobile.tenantId;
|
||||||
this.pkgName = mobile.pkgName;
|
this.pkgName = mobile.pkgName;
|
||||||
|
this.title = mobile.title;
|
||||||
this.appSecret = mobile.appSecret;
|
this.appSecret = mobile.appSecret;
|
||||||
this.platformType = mobile.platformType;
|
this.platformType = mobile.platformType;
|
||||||
this.status = mobile.status;
|
this.status = mobile.status;
|
||||||
|
|||||||
@ -457,6 +457,7 @@ public class ModelConstants {
|
|||||||
*/
|
*/
|
||||||
public static final String MOBILE_APP_TABLE_NAME = "mobile_app";
|
public static final String MOBILE_APP_TABLE_NAME = "mobile_app";
|
||||||
public static final String MOBILE_APP_PKG_NAME_PROPERTY = "pkg_name";
|
public static final String MOBILE_APP_PKG_NAME_PROPERTY = "pkg_name";
|
||||||
|
public static final String MOBILE_APP_TITLE_PROPERTY = "title";
|
||||||
public static final String MOBILE_APP_APP_SECRET_PROPERTY = "app_secret";
|
public static final String MOBILE_APP_APP_SECRET_PROPERTY = "app_secret";
|
||||||
public static final String MOBILE_APP_PLATFORM_TYPE_PROPERTY = "platform_type";
|
public static final String MOBILE_APP_PLATFORM_TYPE_PROPERTY = "platform_type";
|
||||||
public static final String MOBILE_APP_STATUS_PROPERTY = "status";
|
public static final String MOBILE_APP_STATUS_PROPERTY = "status";
|
||||||
|
|||||||
@ -53,6 +53,9 @@ public class MobileAppEntity extends BaseSqlEntity<MobileApp> {
|
|||||||
@Column(name = ModelConstants.MOBILE_APP_PKG_NAME_PROPERTY)
|
@Column(name = ModelConstants.MOBILE_APP_PKG_NAME_PROPERTY)
|
||||||
private String pkgName;
|
private String pkgName;
|
||||||
|
|
||||||
|
@Column(name = ModelConstants.MOBILE_APP_TITLE_PROPERTY)
|
||||||
|
private String title;
|
||||||
|
|
||||||
@Column(name = ModelConstants.MOBILE_APP_APP_SECRET_PROPERTY)
|
@Column(name = ModelConstants.MOBILE_APP_APP_SECRET_PROPERTY)
|
||||||
private String appSecret;
|
private String appSecret;
|
||||||
|
|
||||||
@ -82,6 +85,7 @@ public class MobileAppEntity extends BaseSqlEntity<MobileApp> {
|
|||||||
this.tenantId = mobile.getTenantId().getId();
|
this.tenantId = mobile.getTenantId().getId();
|
||||||
}
|
}
|
||||||
this.pkgName = mobile.getPkgName();
|
this.pkgName = mobile.getPkgName();
|
||||||
|
this.title = mobile.getTitle();
|
||||||
this.appSecret = mobile.getAppSecret();
|
this.appSecret = mobile.getAppSecret();
|
||||||
this.platformType = mobile.getPlatformType();
|
this.platformType = mobile.getPlatformType();
|
||||||
this.status = mobile.getStatus();
|
this.status = mobile.getStatus();
|
||||||
@ -98,6 +102,7 @@ public class MobileAppEntity extends BaseSqlEntity<MobileApp> {
|
|||||||
}
|
}
|
||||||
mobile.setCreatedTime(createdTime);
|
mobile.setCreatedTime(createdTime);
|
||||||
mobile.setPkgName(pkgName);
|
mobile.setPkgName(pkgName);
|
||||||
|
mobile.setTitle(title);
|
||||||
mobile.setAppSecret(appSecret);
|
mobile.setAppSecret(appSecret);
|
||||||
mobile.setPlatformType(platformType);
|
mobile.setPlatformType(platformType);
|
||||||
mobile.setStatus(status);
|
mobile.setStatus(status);
|
||||||
|
|||||||
@ -626,6 +626,7 @@ CREATE TABLE IF NOT EXISTS mobile_app (
|
|||||||
created_time bigint NOT NULL,
|
created_time bigint NOT NULL,
|
||||||
tenant_id uuid,
|
tenant_id uuid,
|
||||||
pkg_name varchar(255),
|
pkg_name varchar(255),
|
||||||
|
title varchar(255),
|
||||||
app_secret varchar(2048),
|
app_secret varchar(2048),
|
||||||
platform_type varchar(32),
|
platform_type varchar(32),
|
||||||
status varchar(32),
|
status varchar(32),
|
||||||
|
|||||||
@ -83,6 +83,7 @@ export class MobileAppTableConfigResolver {
|
|||||||
onAction: (_$event, entity) => entity.pkgName,
|
onAction: (_$event, entity) => entity.pkgName,
|
||||||
type: CellActionDescriptorType.COPY_BUTTON
|
type: CellActionDescriptorType.COPY_BUTTON
|
||||||
}),
|
}),
|
||||||
|
new EntityTableColumn<MobileApp>('title', 'mobile.mobile-package-title', '20%'),
|
||||||
new EntityTableColumn<MobileApp>('appSecret', 'mobile.application-secret', '15%',
|
new EntityTableColumn<MobileApp>('appSecret', 'mobile.application-secret', '15%',
|
||||||
(entity) => this.truncatePipe.transform(entity.appSecret, true, 10, '…'), () => ({}),
|
(entity) => this.truncatePipe.transform(entity.appSecret, true, 10, '…'), () => ({}),
|
||||||
true, () => ({}), () => undefined, false,
|
true, () => ({}), () => undefined, false,
|
||||||
|
|||||||
@ -38,6 +38,14 @@
|
|||||||
{{ 'mobile.mobile-package-pattern' | translate }}
|
{{ 'mobile.mobile-package-pattern' | translate }}
|
||||||
</mat-error>
|
</mat-error>
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
|
<mat-form-field appearance="outline" subscriptSizing="dynamic">
|
||||||
|
<mat-label translate>mobile.mobile-package-title</mat-label>
|
||||||
|
<input matInput formControlName="title">
|
||||||
|
<mat-hint></mat-hint>
|
||||||
|
<mat-error *ngIf="entityForm.get('title').hasError('maxlength')">
|
||||||
|
{{ 'mobile.mobile-package-title-max-length' | translate }}
|
||||||
|
</mat-error>
|
||||||
|
</mat-form-field>
|
||||||
<mat-form-field appearance="outline" class="flex">
|
<mat-form-field appearance="outline" class="flex">
|
||||||
<mat-label translate>mobile.platform-type</mat-label>
|
<mat-label translate>mobile.platform-type</mat-label>
|
||||||
<mat-select formControlName="platformType">
|
<mat-select formControlName="platformType">
|
||||||
|
|||||||
@ -63,9 +63,10 @@ export class MobileAppComponent extends EntityComponent<MobileApp> {
|
|||||||
|
|
||||||
buildForm(entity: MobileApp): FormGroup {
|
buildForm(entity: MobileApp): FormGroup {
|
||||||
const form = this.fb.group({
|
const form = this.fb.group({
|
||||||
pkgName: [entity?.pkgName ? entity.pkgName : '', [Validators.required, Validators.maxLength(255),
|
pkgName: [entity?.pkgName ?? '', [Validators.required, Validators.maxLength(255),
|
||||||
Validators.pattern(/^[a-zA-Z][a-zA-Z\d_]*(?:\.[a-zA-Z][a-zA-Z\d_]*)+$/)]],
|
Validators.pattern(/^[a-zA-Z][a-zA-Z\d_]*(?:\.[a-zA-Z][a-zA-Z\d_]*)+$/)]],
|
||||||
platformType: [entity?.platformType ? entity.platformType : PlatformType.ANDROID],
|
title: [entity?.title ?? '', [Validators.maxLength(255)]],
|
||||||
|
platformType: [entity?.platformType ?? PlatformType.ANDROID],
|
||||||
appSecret: [entity?.appSecret ? entity.appSecret : btoa(randomAlphanumeric(64)), [Validators.required, this.base64Format]],
|
appSecret: [entity?.appSecret ? entity.appSecret : btoa(randomAlphanumeric(64)), [Validators.required, this.base64Format]],
|
||||||
status: [entity?.status ? entity.status : MobileAppStatus.DRAFT],
|
status: [entity?.status ? entity.status : MobileAppStatus.DRAFT],
|
||||||
versionInfo: this.fb.group({
|
versionInfo: this.fb.group({
|
||||||
|
|||||||
@ -43,23 +43,14 @@
|
|||||||
[data]=createMarkDownCommand(gitRepositoryLink)></tb-markdown>
|
[data]=createMarkDownCommand(gitRepositoryLink)></tb-markdown>
|
||||||
</div>
|
</div>
|
||||||
<div class="tb-form-panel stroked">
|
<div class="tb-form-panel stroked">
|
||||||
<div class="tb-form-panel-title" translate>mobile.configuration-step.configure-api-title</div>
|
<div class="tb-form-panel-title" translate>mobile.configuration-step.configure-app-settings-title</div>
|
||||||
<div translate>mobile.configuration-step.configure-api-text</div>
|
<div class="flex gap-4">
|
||||||
<tb-markdown usePlainMarkdown containerClass="tb-command-code"
|
<div class="flex-1" translate>mobile.configuration-step.configure-app-settings-text</div>
|
||||||
[data]=createMarkDownCommand(pathToConstants)></tb-markdown>
|
<button mat-stroked-button color="primary" type="button" (click)="downloadSettings()">
|
||||||
<div translate>mobile.configuration-step.configure-api-hint</div>
|
<mat-icon class="tb-mat-24">download</mat-icon>
|
||||||
<tb-markdown usePlainMarkdown containerClass="tb-command-code"
|
{{ 'mobile.configuration-step.download-file' | translate }}
|
||||||
[data]=createMarkDownCommand(configureApi)></tb-markdown>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="tb-form-panel stroked" *ngIf="setApplication">
|
|
||||||
<div class="tb-form-panel-title" translate>mobile.configuration-step.configure-package-title</div>
|
|
||||||
<div translate>mobile.configuration-step.configure-package-text</div>
|
|
||||||
<div translate>mobile.configuration-step.configure-package-text-install</div>
|
|
||||||
<tb-markdown usePlainMarkdown containerClass="tb-command-code"
|
|
||||||
[data]=createMarkDownCommand(flutterInstallRenameCommand)></tb-markdown>
|
|
||||||
<div translate>mobile.configuration-step.configure-package-run-commands</div>
|
|
||||||
<tb-markdown usePlainMarkdown containerClass="tb-command-code"
|
|
||||||
[data]=createMarkDownCommand(renameCommands)></tb-markdown>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="tb-form-panel stroked">
|
<div class="tb-form-panel stroked">
|
||||||
<div class="tb-form-panel-title" translate>mobile.configuration-step.run-app-title</div>
|
<div class="tb-form-panel-title" translate>mobile.configuration-step.run-app-title</div>
|
||||||
|
|||||||
@ -21,12 +21,15 @@ import { AppState } from '@core/core.state';
|
|||||||
import { Router } from '@angular/router';
|
import { Router } from '@angular/router';
|
||||||
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
|
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
|
||||||
import { ActionPreferencesPutUserSettings } from '@core/auth/auth.actions';
|
import { ActionPreferencesPutUserSettings } from '@core/auth/auth.actions';
|
||||||
import { MobileApp } from '@shared/models/mobile-app.models';
|
import { MobileApp, MobileAppBundleInfo } from '@shared/models/mobile-app.models';
|
||||||
|
import { ImportExportService } from '@shared/import-export/import-export.service';
|
||||||
|
import { isNotEmptyStr } from '@core/utils';
|
||||||
|
|
||||||
export interface MobileAppConfigurationDialogData {
|
export interface MobileAppConfigurationDialogData {
|
||||||
afterAdd: boolean;
|
afterAdd: boolean;
|
||||||
androidApp: MobileApp;
|
androidApp: MobileApp;
|
||||||
iosApp: MobileApp;
|
iosApp: MobileApp;
|
||||||
|
bundle: MobileAppBundleInfo;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
@ -36,53 +39,22 @@ export interface MobileAppConfigurationDialogData {
|
|||||||
})
|
})
|
||||||
export class MobileAppConfigurationDialogComponent extends DialogComponent<MobileAppConfigurationDialogComponent> {
|
export class MobileAppConfigurationDialogComponent extends DialogComponent<MobileAppConfigurationDialogComponent> {
|
||||||
|
|
||||||
notShowAgain = false;
|
private fileName = 'configs';
|
||||||
setApplication = false;
|
|
||||||
|
|
||||||
|
notShowAgain = false;
|
||||||
showDontShowAgain: boolean;
|
showDontShowAgain: boolean;
|
||||||
|
|
||||||
gitRepositoryLink = 'git clone -b master https://github.com/thingsboard/flutter_thingsboard_app.git';
|
gitRepositoryLink = 'git clone -b master https://github.com/thingsboard/flutter_thingsboard_app.git';
|
||||||
pathToConstants = 'lib/constants/app_constants.dart';
|
flutterRunCommand = `flutter run --dart-define-from-file ${this.fileName}.json`;
|
||||||
flutterRunCommand = 'flutter run';
|
|
||||||
flutterInstallRenameCommand = 'flutter pub global activate rename';
|
|
||||||
|
|
||||||
configureApi: string;
|
|
||||||
|
|
||||||
renameCommands: string[] = [];
|
|
||||||
|
|
||||||
constructor(protected store: Store<AppState>,
|
constructor(protected store: Store<AppState>,
|
||||||
protected router: Router,
|
protected router: Router,
|
||||||
@Inject(MAT_DIALOG_DATA) private data: MobileAppConfigurationDialogData,
|
@Inject(MAT_DIALOG_DATA) private data: MobileAppConfigurationDialogData,
|
||||||
protected dialogRef: MatDialogRef<MobileAppConfigurationDialogComponent>,
|
protected dialogRef: MatDialogRef<MobileAppConfigurationDialogComponent>,
|
||||||
|
private importExportService: ImportExportService,
|
||||||
) {
|
) {
|
||||||
super(store, router, dialogRef);
|
super(store, router, dialogRef);
|
||||||
|
|
||||||
this.showDontShowAgain = this.data.afterAdd;
|
this.showDontShowAgain = this.data.afterAdd;
|
||||||
|
|
||||||
this.setApplication = !!this.data.androidApp || !!this.data.iosApp;
|
|
||||||
|
|
||||||
this.configureApi = `static const thingsBoardApiEndpoint = '${window.location.origin}';`;
|
|
||||||
if (this.setApplication) {
|
|
||||||
this.configureApi += '\n';
|
|
||||||
if (!!this.data.androidApp) {
|
|
||||||
this.configureApi += `\nstatic const thingsboardAndroidAppSecret = '${this.data.androidApp.appSecret}';`;
|
|
||||||
}
|
|
||||||
if (!!this.data.iosApp) {
|
|
||||||
this.configureApi += `\nstatic const thingsboardIOSAppSecret = '${this.data.iosApp.appSecret}';`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (this.setApplication) {
|
|
||||||
if (this.data.androidApp?.pkgName === this.data.iosApp?.pkgName) {
|
|
||||||
this.renameCommands.push(`rename setBundleId --targets android, ios --value "${this.data.androidApp.pkgName}"`);
|
|
||||||
} else {
|
|
||||||
if (!!this.data.androidApp) {
|
|
||||||
this.renameCommands.push(`rename setBundleId --targets android --value "${this.data.androidApp.pkgName}"`);
|
|
||||||
}
|
|
||||||
if (!!this.data.iosApp) {
|
|
||||||
this.renameCommands.push(`rename setBundleId --targets ios --value "${this.data.iosApp.pkgName}"`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
close(): void {
|
close(): void {
|
||||||
@ -94,14 +66,28 @@ export class MobileAppConfigurationDialogComponent extends DialogComponent<Mobil
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
createMarkDownCommand(commands: string | string[]): string {
|
createMarkDownCommand(commands: 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);
|
return this.createMarkDownSingleCommand(commands);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
downloadSettings(): void {
|
||||||
|
const settings: any = {
|
||||||
|
thingsboardApiEndpoint: window.location.origin,
|
||||||
|
appLinksUrlHost: window.location.host,
|
||||||
|
appLinksUrlScheme: window.location.protocol.slice(0, -1),
|
||||||
|
};
|
||||||
|
if (!!this.data.androidApp) {
|
||||||
|
settings.androidApplicationId = this.data.androidApp.pkgName;
|
||||||
|
settings.androidApplicationName = isNotEmptyStr(this.data.androidApp.title) ? this.data.androidApp.title : this.data.bundle.title;
|
||||||
|
settings.thingsboardOAuth2CallbackUrlScheme = this.data.androidApp.pkgName + '.auth';
|
||||||
|
settings.thingsboardAndroidAppSecret = this.data.androidApp.appSecret;
|
||||||
|
}
|
||||||
|
if (!!this.data.iosApp) {
|
||||||
|
settings.iosApplicationId = this.data.iosApp.pkgName;
|
||||||
|
settings.iosApplicationName = isNotEmptyStr(this.data.iosApp.title) ? this.data.iosApp.title : this.data.bundle.title;
|
||||||
|
settings.thingsboardIosAppSecret = this.data.iosApp.appSecret;
|
||||||
|
}
|
||||||
|
this.importExportService.exportJson(settings, this.fileName);
|
||||||
}
|
}
|
||||||
|
|
||||||
private createMarkDownSingleCommand(command: string): string {
|
private createMarkDownSingleCommand(command: string): string {
|
||||||
|
|||||||
@ -182,7 +182,8 @@ export class MobileBundleTableConfigResolver {
|
|||||||
data: {
|
data: {
|
||||||
afterAdd,
|
afterAdd,
|
||||||
androidApp: data.androidApp,
|
androidApp: data.androidApp,
|
||||||
iosApp: data.iosApp
|
iosApp: data.iosApp,
|
||||||
|
bundle: entity
|
||||||
}
|
}
|
||||||
}).afterClosed()
|
}).afterClosed()
|
||||||
.subscribe();
|
.subscribe();
|
||||||
|
|||||||
@ -1188,7 +1188,7 @@ export class ImportExportService {
|
|||||||
this.exportJson(data, filename);
|
this.exportJson(data, filename);
|
||||||
}
|
}
|
||||||
|
|
||||||
private exportJson(data: any, filename: string) {
|
public exportJson(data: any, filename: string) {
|
||||||
if (isObject(data)) {
|
if (isObject(data)) {
|
||||||
data = JSON.stringify(data, null, 2);
|
data = JSON.stringify(data, null, 2);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -85,6 +85,7 @@ export interface StoreInfo {
|
|||||||
|
|
||||||
export interface MobileApp extends BaseData<MobileAppId>, HasTenantId {
|
export interface MobileApp extends BaseData<MobileAppId>, HasTenantId {
|
||||||
pkgName: string;
|
pkgName: string;
|
||||||
|
title?: string;
|
||||||
appSecret: string;
|
appSecret: string;
|
||||||
platformType: PlatformType;
|
platformType: PlatformType;
|
||||||
status: MobileAppStatus;
|
status: MobileAppStatus;
|
||||||
|
|||||||
@ -3847,6 +3847,8 @@
|
|||||||
"mobile-package-max-length": "Application package should be less than 256",
|
"mobile-package-max-length": "Application package should be less than 256",
|
||||||
"mobile-package-required": "Application package is required.",
|
"mobile-package-required": "Application package is required.",
|
||||||
"mobile-package-pattern": "Application package invalid format",
|
"mobile-package-pattern": "Application package invalid format",
|
||||||
|
"mobile-package-title": "Application title",
|
||||||
|
"mobile-package-title-max-length": "Application title should be less than 256",
|
||||||
"no-application": "No applications found",
|
"no-application": "No applications found",
|
||||||
"no-bundles": "No bundles found",
|
"no-bundles": "No bundles found",
|
||||||
"platform-type": "Platform type",
|
"platform-type": "Platform type",
|
||||||
@ -3930,17 +3932,13 @@
|
|||||||
"prepare-environment-text": "Flutter ThingsBoard Mobile Application requires Flutter SDK. Follow instructions to set up Flutter SDK.",
|
"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-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:",
|
"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-app-settings-title": "Configure app settings",
|
||||||
"configure-api-text": "Open the flutter_thingsboard_app project in your editor/IDE. Edit:",
|
"configure-app-settings-text": "Download the configuration file and place it into the root directory of the project you cloned in the previous step.",
|
||||||
"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.",
|
"download-file": "Download file",
|
||||||
"run-app-title": "Run the app",
|
"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:",
|
"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.",
|
"more-information": "Detailed information may be found in our Getting Started documentation.",
|
||||||
"getting-started": "Getting Started",
|
"getting-started": "Getting Started"
|
||||||
"configure-package-title": "Configure application package",
|
|
||||||
"configure-package-text": "You can manually change the Application Package or use third party CLI tool.",
|
|
||||||
"configure-package-text-install": "To install the Rename CLI Tool, execute the following command:",
|
|
||||||
"configure-package-run-commands": "Run these commands in the root directory of your project:"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"notification": {
|
"notification": {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user