UI: Add remove and config mobile app dialog; Fixed input colors

This commit is contained in:
Vladyslav_Prykhodko 2024-10-24 18:12:06 +03:00
parent 714152ce91
commit b0e11e130a
12 changed files with 355 additions and 7 deletions

View File

@ -23,6 +23,10 @@ import { HomeComponentsModule } from '@home/components/home-components.module';
import { ApplicationsRoutingModule } from '@home/pages/mobile/applications/applications-routing.module'; import { ApplicationsRoutingModule } from '@home/pages/mobile/applications/applications-routing.module';
import { ReleaseNotesPanelComponent } from '@home/pages/mobile/applications/release-notes-panel.component'; import { ReleaseNotesPanelComponent } from '@home/pages/mobile/applications/release-notes-panel.component';
import { MobileAppDialogComponent } from '@home/pages/mobile/applications/mobile-app-dialog.component'; import { MobileAppDialogComponent } from '@home/pages/mobile/applications/mobile-app-dialog.component';
import { RemoveAppDialogComponent } from '@home/pages/mobile/applications/remove-app-dialog.component';
import {
MobileAppConfigurationDialogComponent
} from '@home/pages/mobile/applications/mobile-app-configuration-dialog.component';
@NgModule({ @NgModule({
declarations: [ declarations: [
@ -30,6 +34,8 @@ import { MobileAppDialogComponent } from '@home/pages/mobile/applications/mobile
MobileAppTableHeaderComponent, MobileAppTableHeaderComponent,
ReleaseNotesPanelComponent, ReleaseNotesPanelComponent,
MobileAppDialogComponent, MobileAppDialogComponent,
RemoveAppDialogComponent,
MobileAppConfigurationDialogComponent,
], ],
imports: [ imports: [
CommonModule, CommonModule,

View File

@ -0,0 +1,36 @@
<!--
Copyright © 2016-2024 The Thingsboard Authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<mat-toolbar color="primary" class="justify-between">
<h2 translate>mobile.configuration-dialog</h2>
<button mat-icon-button
(click)="close()"
type="button">
<mat-icon class="material-icons">close</mat-icon>
</button>
</mat-toolbar>
<div mat-dialog-content>
</div>
<div mat-dialog-actions class="tb-dialog-actions">
<mat-slide-toggle [class.!hidden]="!showDontShowAgain" [(ngModel)]="notShowAgain">{{ 'action.dont-show-again' | translate}}</mat-slide-toggle>
<span class="flex-1"></span>
<button mat-button
[disabled]="(isLoading$ | async)"
(click)="close()">{{ 'action.close' | translate }}</button>
</div>

View File

@ -0,0 +1,22 @@
/**
* Copyright © 2016-2024 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
:host {
height: 100%;
max-height: 100vh;
display: grid;
width: 800px;
grid-template-rows: min-content minmax(auto, 1fr) min-content;
}

View File

@ -0,0 +1,58 @@
///
/// Copyright © 2016-2024 The Thingsboard Authors
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
///
import { Component, Inject } from '@angular/core';
import { DialogComponent } from '@shared/components/dialog.component';
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';
export interface MobileAppConfigurationDialogData {
afterAdd: boolean;
}
@Component({
selector: 'tb-mobile-app-configuration-dialog',
templateUrl: './mobile-app-configuration-dialog.component.html',
styleUrls: ['./mobile-app-configuration-dialog.component.scss']
})
export class MobileAppConfigurationDialogComponent extends DialogComponent<MobileAppConfigurationDialogComponent> {
notShowAgain = false;
showDontShowAgain: boolean;
constructor(protected store: Store<AppState>,
protected router: Router,
@Inject(MAT_DIALOG_DATA) private data: MobileAppConfigurationDialogData,
protected dialogRef: MatDialogRef<MobileAppConfigurationDialogComponent>,
) {
super(store, router, dialogRef);
this.showDontShowAgain = this.data.afterAdd;
}
close(): void {
if (this.notShowAgain && this.showDontShowAgain) {
// this.store.dispatch(new ActionPreferencesPutUserSettings({ notDisplayConnectivityAfterAddDevice: true }));
this.dialogRef.close(null);
} else {
this.dialogRef.close(null);
}
}
}

View File

@ -17,6 +17,7 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { ActivatedRouteSnapshot } from '@angular/router'; import { ActivatedRouteSnapshot } from '@angular/router';
import { import {
CellActionDescriptor,
CellActionDescriptorType, CellActionDescriptorType,
DateEntityTableColumn, DateEntityTableColumn,
EntityTableColumn, EntityTableColumn,
@ -29,9 +30,28 @@ import { Direction } from '@app/shared/models/page/sort-order';
import { MobileAppService } from '@core/http/mobile-app.service'; import { MobileAppService } from '@core/http/mobile-app.service';
import { MobileAppComponent } from '@home/pages/mobile/applications/mobile-app.component'; import { MobileAppComponent } from '@home/pages/mobile/applications/mobile-app.component';
import { MobileAppTableHeaderComponent } from '@home/pages/mobile/applications/mobile-app-table-header.component'; import { MobileAppTableHeaderComponent } from '@home/pages/mobile/applications/mobile-app-table-header.component';
import { MobileApp, MobileAppStatus, mobileAppStatusTranslations } from '@shared/models/mobile-app.models'; import {
MobileApp,
MobileAppBundleInfo,
MobileAppStatus,
mobileAppStatusTranslations
} from '@shared/models/mobile-app.models';
import { platformTypeTranslations } from '@shared/models/oauth2.models'; import { platformTypeTranslations } from '@shared/models/oauth2.models';
import { TruncatePipe } from '@shared/pipe/truncate.pipe'; import { TruncatePipe } from '@shared/pipe/truncate.pipe';
import { NotificationTemplate } from '@shared/models/notification.models';
import { BaseData, HasId } from '@shared/models/base-data';
import {
MobileBundleDialogComponent,
MobileBundleDialogData
} from '@home/pages/mobile/bundes/mobile-bundle-dialog.component';
import { MatDialog } from '@angular/material/dialog';
import {
MobileAppDeleteDialogData,
RemoveAppDialogComponent
} from '@home/pages/mobile/applications/remove-app-dialog.component';
import {
MobileAppConfigurationDialogComponent, MobileAppConfigurationDialogData
} from '@home/pages/mobile/applications/mobile-app-configuration-dialog.component';
@Injectable() @Injectable()
export class MobileAppTableConfigResolver { export class MobileAppTableConfigResolver {
@ -41,10 +61,12 @@ export class MobileAppTableConfigResolver {
constructor(private translate: TranslateService, constructor(private translate: TranslateService,
private datePipe: DatePipe, private datePipe: DatePipe,
private mobileAppService: MobileAppService, private mobileAppService: MobileAppService,
private truncatePipe: TruncatePipe) { private truncatePipe: TruncatePipe,
private dialog: MatDialog,) {
this.config.selectionEnabled = false; this.config.selectionEnabled = false;
this.config.entityType = EntityType.MOBILE_APP; this.config.entityType = EntityType.MOBILE_APP;
this.config.addEnabled = false; this.config.addEnabled = false;
this.config.entitiesDeleteEnabled = false;
this.config.rowPointer = true; this.config.rowPointer = true;
this.config.entityTranslations = entityTypeTranslations.get(EntityType.MOBILE_APP); this.config.entityTranslations = entityTypeTranslations.get(EntityType.MOBILE_APP);
this.config.entityResources = entityTypeResources.get(EntityType.MOBILE_APP); this.config.entityResources = entityTypeResources.get(EntityType.MOBILE_APP);
@ -97,18 +119,73 @@ export class MobileAppTableConfigResolver {
(entity) => entity.versionInfo?.latestVersion ?? '', () => ({}), false), (entity) => entity.versionInfo?.latestVersion ?? '', () => ({}), false),
); );
this.config.deleteEntityTitle = (app) => this.translate.instant('mobile.delete-applications-title', {applicationName: app.pkgName});
this.config.deleteEntityContent = () => this.translate.instant('mobile.delete-applications-text');
this.config.entitiesFetchFunction = pageLink => this.mobileAppService.getTenantMobileAppInfos(pageLink); this.config.entitiesFetchFunction = pageLink => this.mobileAppService.getTenantMobileAppInfos(pageLink);
this.config.loadEntity = id => this.mobileAppService.getMobileAppInfoById(id.id); this.config.loadEntity = id => this.mobileAppService.getMobileAppInfoById(id.id);
this.config.saveEntity = (mobileApp) => this.mobileAppService.saveMobileApp(mobileApp); this.config.saveEntity = (mobileApp) => this.mobileAppService.saveMobileApp(mobileApp);
this.config.deleteEntity = id => this.mobileAppService.deleteMobileApp(id.id); this.config.deleteEntity = id => this.mobileAppService.deleteMobileApp(id.id);
this.config.cellActionDescriptors = this.configureCellActions();
} }
resolve(_route: ActivatedRouteSnapshot): EntityTableConfig<MobileApp> { resolve(_route: ActivatedRouteSnapshot): EntityTableConfig<MobileApp> {
return this.config; return this.config;
} }
private configureCellActions(): Array<CellActionDescriptor<MobileApp>> {
return [
{
name: this.translate.instant('mobile.configuration-app'),
icon: 'code',
isEnabled: () => true,
onAction: ($event, entity) => this.configurationApp($event, entity)
},
{
name: this.translate.instant('action.delete'),
icon: 'delete',
isEnabled: () => true,
onAction: ($event, entity) => this.deleteEntity($event, entity)
}
];
}
private deleteEntity($event: Event, entity: MobileApp) {
if ($event) {
$event.stopPropagation();
}
this.dialog.open<RemoveAppDialogComponent, MobileAppDeleteDialogData,
MobileAppBundleInfo>(RemoveAppDialogComponent, {
disableClose: true,
panelClass: ['tb-dialog', 'tb-fullscreen-dialog'],
data: {
id: entity.id.id
}
}).afterClosed()
.subscribe((res) => {
if (res) {
this.config.updateData();
}
});
}
private configurationApp($event: Event, entity: MobileApp, afterAdd = false) {
if ($event) {
$event.stopPropagation();
}
this.dialog.open<MobileAppConfigurationDialogComponent, MobileAppConfigurationDialogData,
MobileAppBundleInfo>(MobileAppConfigurationDialogComponent, {
disableClose: true,
panelClass: ['tb-dialog', 'tb-fullscreen-dialog'],
data: {
afterAdd
}
}).afterClosed()
.subscribe(() => {
if (afterAdd) {
this.config.updateData();
}
});
}
private mobileStatus(status: MobileAppStatus): string { private mobileStatus(status: MobileAppStatus): string {
const translateKey = mobileAppStatusTranslations.get(status); const translateKey = mobileAppStatusTranslations.get(status);
let backgroundColor = 'rgba(25, 128, 56, 0.06)'; let backgroundColor = 'rgba(25, 128, 56, 0.06)';

View File

@ -0,0 +1,48 @@
<!--
Copyright © 2016-2024 The Thingsboard Authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<mat-toolbar class="justify-between" color="primary">
<h2 translate>mobile.delete-application</h2>
<button mat-icon-button
(click)="cancel()"
type="button">
<mat-icon class="material-icons">close</mat-icon>
</button>
</mat-toolbar>
<mat-progress-bar color="warn" mode="indeterminate" *ngIf="isLoading$ | async">
</mat-progress-bar>
<div *ngIf="!(isLoading$ | async)"></div>
<div mat-dialog-content class="flex flex-col gap-4">
<div class="select-none" [innerHTML]="deleteApplicationText"></div>
<mat-form-field appearance="outline" subscriptSizing="dynamic">
<input matInput [formControl]="deleteVerification" placeholder="{{ 'mobile.type-here' | translate }}"/>
</mat-form-field>
</div>
<div mat-dialog-actions class="justify-end">
<button mat-button color="primary"
type="button"
[disabled]="(isLoading$ | async)"
(click)="cancel()" cdkFocusInitial>
{{ 'action.suspend' | translate }}
</button>
<button mat-button mat-raised-button color="warn"
type="button"
(click)="delete()"
[disabled]="(isLoading$ | async) || deleteVerification.invalid || deleteVerification.value !== deleteVerificationText">
{{ 'mobile.delete-application-button-text' | translate }}
</button>
</div>

View File

@ -0,0 +1,24 @@
/**
* Copyright © 2016-2024 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
:host{
width: 750px;
height: 100%;
max-width: 100%;
max-height: 100vh;
display: grid;
grid-template-rows: min-content 4px minmax(auto, 1fr) min-content;
--mdc-outlined-text-field-outline-color: rgba(0,0,0,0.12);
}

View File

@ -0,0 +1,68 @@
///
/// Copyright © 2016-2024 The Thingsboard Authors
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
///
import { Component, Inject } from '@angular/core';
import { DialogComponent } from '@shared/components/dialog.component';
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 { TranslateService } from '@ngx-translate/core';
import { DomSanitizer, SafeHtml } from '@angular/platform-browser';
import { FormBuilder } from '@angular/forms';
import { MobileAppService } from '@core/http/mobile-app.service';
export interface MobileAppDeleteDialogData {
id: string;
}
@Component({
selector: 'tb-remove-app-dialog',
templateUrl: './remove-app-dialog.component.html',
styleUrls: ['./remove-app-dialog.component.scss']
})
export class RemoveAppDialogComponent extends DialogComponent<RemoveAppDialogComponent, boolean> {
readonly deleteApplicationText: SafeHtml;
readonly deleteVerificationText: string;
deleteVerification = this.fb.control('');
constructor(protected store: Store<AppState>,
protected router: Router,
protected dialogRef: MatDialogRef<RemoveAppDialogComponent, boolean>,
@Inject(MAT_DIALOG_DATA) private data: MobileAppDeleteDialogData,
private translate: TranslateService,
private sanitizer: DomSanitizer,
private fb: FormBuilder,
private mobileAppService: MobileAppService,) {
super(store, router, dialogRef);
this.deleteVerificationText = this.translate.instant('mobile.delete-application-phrase');
this.deleteApplicationText = this.sanitizer.bypassSecurityTrustHtml(
this.translate.instant('mobile.delete-application-text', {phrase: this.deleteVerificationText})
)
}
cancel(): void {
this.dialogRef.close(false);
}
delete(): void {
this.mobileAppService.deleteMobileApp(this.data.id).subscribe(() => {
this.dialogRef.close(true);
});
}
}

View File

@ -14,6 +14,8 @@
* limitations under the License. * limitations under the License.
*/ */
:host{ :host{
--mdc-outlined-text-field-outline-color: rgba(0,0,0,0.12);
.mobile-page-form { .mobile-page-form {
display: flex; display: flex;
flex-direction: column; flex-direction: column;

View File

@ -20,6 +20,7 @@
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 16px; gap: 16px;
--mdc-outlined-text-field-outline-color: rgba(0,0,0,0.12);
@media #{$mat-lt-md} { @media #{$mat-lt-md} {
width: 90vw; width: 90vw;
} }

View File

@ -22,6 +22,7 @@
max-height: 100vh; max-height: 100vh;
display: grid; display: grid;
grid-template-rows: min-content 4px minmax(auto, 1fr) min-content; grid-template-rows: min-content 4px minmax(auto, 1fr) min-content;
--mdc-outlined-text-field-outline-color: rgba(0,0,0,0.12);
.mat-mdc-slide-toggle { .mat-mdc-slide-toggle {
margin-bottom: 16px; margin-bottom: 16px;

View File

@ -3405,8 +3405,10 @@
"copy-application-secret": "Copy application secret", "copy-application-secret": "Copy application secret",
"copy-google-play-link": "Copy Google Play link", "copy-google-play-link": "Copy Google Play link",
"copy-sha256-certificate-fingerprints": "Copy SHA256 certificate fingerprints", "copy-sha256-certificate-fingerprints": "Copy SHA256 certificate fingerprints",
"delete-applications-text": "Be careful, after the confirmation the mobile application and all related data will become unrecoverable.", "delete-application": "Delete application",
"delete-applications-title": "Are you sure you want to delete the mobile application '{{applicationName}}'?", "delete-application-button-text": "I understand consequences, delete application",
"delete-application-text": "This action can not be undone. This will permanently delete your application.<br/>If you dont want to delete it permanently you can <b>suspend</b> application temporary.<br/><b>To delete</b> the application anyway please type <b>\"{{phrase}}\"</b> to confirm.",
"delete-application-phrase": "delete application",
"delete-applications-bundle-text": "Be careful, after the confirmation the mobile bundle and all related data will become unrecoverable.", "delete-applications-bundle-text": "Be careful, after the confirmation the mobile bundle and all related data will become unrecoverable.",
"delete-applications-bundle-title": "Are you sure you want to delete the mobile bundle '{{bundleName}}'?", "delete-applications-bundle-title": "Are you sure you want to delete the mobile bundle '{{bundleName}}'?",
"generate-application-secret": "Generate application secret", "generate-application-secret": "Generate application secret",
@ -3484,7 +3486,10 @@
"edit-page": "Edit page", "edit-page": "Edit page",
"edit-custom-page": "Edit custom page", "edit-custom-page": "Edit custom page",
"delete-page": "Delete page", "delete-page": "Delete page",
"qr-code-widget": "QR code widget" "qr-code-widget": "QR code widget",
"type-here": "Type hero",
"configuration-dialog": "Configuration dialog",
"configuration-app": "Configuration app"
}, },
"notification": { "notification": {
"action-button": "Action button", "action-button": "Action button",