UI: Add release note to mobile application
This commit is contained in:
parent
119354a441
commit
ed24383e2a
@ -21,11 +21,13 @@ import { CommonModule } from '@angular/common';
|
|||||||
import { SharedModule } from '@shared/shared.module';
|
import { SharedModule } from '@shared/shared.module';
|
||||||
import { HomeComponentsModule } from '@home/components/home-components.module';
|
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';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
declarations: [
|
declarations: [
|
||||||
MobileAppComponent,
|
MobileAppComponent,
|
||||||
MobileAppTableHeaderComponent
|
MobileAppTableHeaderComponent,
|
||||||
|
ReleaseNotesPanelComponent
|
||||||
],
|
],
|
||||||
imports: [
|
imports: [
|
||||||
CommonModule,
|
CommonModule,
|
||||||
|
|||||||
@ -120,7 +120,7 @@ export class MobileAppTableConfigResolver {
|
|||||||
backgroundColor = 'rgba(209, 39, 48, 0.06)';
|
backgroundColor = 'rgba(209, 39, 48, 0.06)';
|
||||||
break;
|
break;
|
||||||
case MobileAppStatus.DRAFT:
|
case MobileAppStatus.DRAFT:
|
||||||
backgroundColor = 'rgba(160, 160, 160, 0.06)';
|
backgroundColor = 'rgba(0, 148, 255, 0.06)';
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
return `<div style="border-radius: 14px; height: 28px; line-height: 20px; padding: 4px 10px;
|
return `<div style="border-radius: 14px; height: 28px; line-height: 20px; padding: 4px 10px;
|
||||||
@ -142,7 +142,7 @@ export class MobileAppTableConfigResolver {
|
|||||||
styleObj.color = '#D12730';
|
styleObj.color = '#D12730';
|
||||||
break;
|
break;
|
||||||
case MobileAppStatus.DRAFT:
|
case MobileAppStatus.DRAFT:
|
||||||
styleObj.color = '#A0A0A0';
|
styleObj.color = '#0094FF';
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
return styleObj;
|
return styleObj;
|
||||||
|
|||||||
@ -68,6 +68,9 @@
|
|||||||
</tb-copy-button>
|
</tb-copy-button>
|
||||||
</div>
|
</div>
|
||||||
<mat-hint> </mat-hint>
|
<mat-hint> </mat-hint>
|
||||||
|
<mat-error *ngIf="entityForm.get('appSecret').hasError('required')">
|
||||||
|
{{ 'mobile.application-secret-required' | translate }}
|
||||||
|
</mat-error>
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
<mat-form-field appearance="outline">
|
<mat-form-field appearance="outline">
|
||||||
<mat-label translate>mobile.status</mat-label>
|
<mat-label translate>mobile.status</mat-label>
|
||||||
@ -80,14 +83,36 @@
|
|||||||
<section class="tb-form-panel stroked no-padding-bottom" formGroupName="versionInfo" style="margin-bottom: 21px;">
|
<section class="tb-form-panel stroked no-padding-bottom" formGroupName="versionInfo" style="margin-bottom: 21px;">
|
||||||
<div class="tb-form-panel-title" translate>mobile.version-information</div>
|
<div class="tb-form-panel-title" translate>mobile.version-information</div>
|
||||||
<section fxLayout="column">
|
<section fxLayout="column">
|
||||||
<mat-form-field appearance="outline">
|
<section fxLayout="row" fxLayoutAlign="start start">
|
||||||
|
<mat-form-field fxFlex appearance="outline">
|
||||||
<mat-label translate>mobile.min-version</mat-label>
|
<mat-label translate>mobile.min-version</mat-label>
|
||||||
<input matInput formControlName="minVersion">
|
<input matInput formControlName="minVersion">
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
<mat-form-field appearance="outline">
|
<button mat-icon-button
|
||||||
|
style="margin-top: 4px; color:#00000061"
|
||||||
|
type="button"
|
||||||
|
matTooltip="{{ 'mobile.min-version-release-notes' | translate }}"
|
||||||
|
matTooltipPosition="above"
|
||||||
|
#editMinReleaseNotesVersionButton
|
||||||
|
(click)="editReleaseNote($event, editMinReleaseNotesVersionButton, false)">
|
||||||
|
<tb-icon>mdi:text-box-edit</tb-icon>
|
||||||
|
</button>
|
||||||
|
</section>
|
||||||
|
<section fxLayout="row" fxLayoutAlign="start start">
|
||||||
|
<mat-form-field fxFlex appearance="outline">
|
||||||
<mat-label translate>mobile.latest-version</mat-label>
|
<mat-label translate>mobile.latest-version</mat-label>
|
||||||
<input matInput formControlName="latestVersion">
|
<input matInput formControlName="latestVersion">
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
|
<button mat-icon-button
|
||||||
|
style="margin-top: 4px; color:#00000061"
|
||||||
|
type="button"
|
||||||
|
matTooltip="{{ 'mobile.latest-version-release-notes' | translate }}"
|
||||||
|
matTooltipPosition="above"
|
||||||
|
#editLatestReleaseNotesVersionButton
|
||||||
|
(click)="editReleaseNote($event, editLatestReleaseNotesVersionButton, true)">
|
||||||
|
<tb-icon>mdi:text-box-edit</tb-icon>
|
||||||
|
</button>
|
||||||
|
</section>
|
||||||
</section>
|
</section>
|
||||||
</section>
|
</section>
|
||||||
<section class="tb-form-panel stroked no-padding-bottom" formGroupName="storeInfo">
|
<section class="tb-form-panel stroked no-padding-bottom" formGroupName="storeInfo">
|
||||||
|
|||||||
@ -14,7 +14,7 @@
|
|||||||
/// limitations under the License.
|
/// limitations under the License.
|
||||||
///
|
///
|
||||||
|
|
||||||
import { ChangeDetectorRef, Component, Inject } from '@angular/core';
|
import { ChangeDetectorRef, Component, Inject, Renderer2, ViewContainerRef } from '@angular/core';
|
||||||
import { EntityComponent } from '@home/components/entity/entity.component';
|
import { EntityComponent } from '@home/components/entity/entity.component';
|
||||||
import { AppState } from '@core/core.state';
|
import { AppState } from '@core/core.state';
|
||||||
import { EntityTableConfig } from '@home/models/entity/entities-table-config.models';
|
import { EntityTableConfig } from '@home/models/entity/entities-table-config.models';
|
||||||
@ -26,6 +26,9 @@ import { EntityType } from '@shared/models/entity-type.models';
|
|||||||
import { MobileApp, MobileAppStatus, mobileAppStatusTranslations } from '@shared/models/mobile-app.models';
|
import { MobileApp, MobileAppStatus, mobileAppStatusTranslations } from '@shared/models/mobile-app.models';
|
||||||
import { PlatformType, platformTypeTranslations } from '@shared/models/oauth2.models';
|
import { PlatformType, platformTypeTranslations } from '@shared/models/oauth2.models';
|
||||||
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
||||||
|
import { MatButton } from '@angular/material/button';
|
||||||
|
import { TbPopoverService } from '@shared/components/popover.service';
|
||||||
|
import { ReleaseNotesPanelComponent } from '@home/pages/mobile/applications/release-notes-panel.component';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'tb-mobile-app',
|
selector: 'tb-mobile-app',
|
||||||
@ -51,7 +54,10 @@ export class MobileAppComponent extends EntityComponent<MobileApp> {
|
|||||||
@Inject('entity') protected entityValue: MobileApp,
|
@Inject('entity') protected entityValue: MobileApp,
|
||||||
@Inject('entitiesTableConfig') protected entitiesTableConfigValue: EntityTableConfig<MobileApp>,
|
@Inject('entitiesTableConfig') protected entitiesTableConfigValue: EntityTableConfig<MobileApp>,
|
||||||
protected cd: ChangeDetectorRef,
|
protected cd: ChangeDetectorRef,
|
||||||
public fb: FormBuilder) {
|
public fb: FormBuilder,
|
||||||
|
private popoverService: TbPopoverService,
|
||||||
|
private renderer: Renderer2,
|
||||||
|
private viewContainerRef: ViewContainerRef) {
|
||||||
super(store, fb, entityValue, entitiesTableConfigValue, cd);
|
super(store, fb, entityValue, entitiesTableConfigValue, cd);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -64,7 +70,9 @@ export class MobileAppComponent extends EntityComponent<MobileApp> {
|
|||||||
status: [entity?.status ? entity.status : MobileAppStatus.DRAFT],
|
status: [entity?.status ? entity.status : MobileAppStatus.DRAFT],
|
||||||
versionInfo: this.fb.group({
|
versionInfo: this.fb.group({
|
||||||
minVersion: [entity?.versionInfo?.minVersion ? entity.versionInfo.minVersion : ''],
|
minVersion: [entity?.versionInfo?.minVersion ? entity.versionInfo.minVersion : ''],
|
||||||
|
minVersionReleaseNotes: [entity?.versionInfo?.minVersionReleaseNotes ? entity.versionInfo.minVersionReleaseNotes : ''],
|
||||||
latestVersion: [entity?.versionInfo?.latestVersion ? entity.versionInfo.latestVersion : ''],
|
latestVersion: [entity?.versionInfo?.latestVersion ? entity.versionInfo.latestVersion : ''],
|
||||||
|
latestVersionReleaseNotes: [entity?.versionInfo?.latestVersionReleaseNotes ? entity.versionInfo.latestVersionReleaseNotes : ''],
|
||||||
}),
|
}),
|
||||||
storeInfo: this.fb.group({
|
storeInfo: this.fb.group({
|
||||||
storeLink: [entity?.storeInfo?.storeLink ? entity.storeInfo.storeLink : ''],
|
storeLink: [entity?.storeInfo?.storeLink ? entity.storeInfo.storeLink : ''],
|
||||||
@ -89,7 +97,7 @@ export class MobileAppComponent extends EntityComponent<MobileApp> {
|
|||||||
form.get('status').valueChanges.pipe(
|
form.get('status').valueChanges.pipe(
|
||||||
takeUntilDestroyed()
|
takeUntilDestroyed()
|
||||||
).subscribe((value: MobileAppStatus) => {
|
).subscribe((value: MobileAppStatus) => {
|
||||||
if (value === MobileAppStatus.PUBLISHED) {
|
if (value !== MobileAppStatus.DRAFT) {
|
||||||
form.get('storeInfo.storeLink').addValidators(Validators.required);
|
form.get('storeInfo.storeLink').addValidators(Validators.required);
|
||||||
form.get('storeInfo.sha256CertFingerprints').addValidators(Validators.required);
|
form.get('storeInfo.sha256CertFingerprints').addValidators(Validators.required);
|
||||||
form.get('storeInfo.appId').addValidators(Validators.required);
|
form.get('storeInfo.appId').addValidators(Validators.required);
|
||||||
@ -113,6 +121,7 @@ export class MobileAppComponent extends EntityComponent<MobileApp> {
|
|||||||
override updateFormState(): void {
|
override updateFormState(): void {
|
||||||
super.updateFormState();
|
super.updateFormState();
|
||||||
if (this.isEdit && this.entityForm && !this.isAdd) {
|
if (this.isEdit && this.entityForm && !this.isAdd) {
|
||||||
|
this.entityForm.get('status').updateValueAndValidity({onlySelf: false});
|
||||||
this.entityForm.get('platformType').disable({emitEvent: false});
|
this.entityForm.get('platformType').disable({emitEvent: false});
|
||||||
if (this.entityForm.get('platformType').value === PlatformType.ANDROID) {
|
if (this.entityForm.get('platformType').value === PlatformType.ANDROID) {
|
||||||
this.entityForm.get('storeInfo.appId').disable({emitEvent: false});
|
this.entityForm.get('storeInfo.appId').disable({emitEvent: false});
|
||||||
@ -136,6 +145,40 @@ export class MobileAppComponent extends EntityComponent<MobileApp> {
|
|||||||
this.entityForm.get('appSecret').markAsDirty();
|
this.entityForm.get('appSecret').markAsDirty();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
editReleaseNote($event: Event, matButton: MatButton, isLatest: boolean) {
|
||||||
|
if ($event) {
|
||||||
|
$event.stopPropagation();
|
||||||
|
}
|
||||||
|
const trigger = matButton._elementRef.nativeElement;
|
||||||
|
if (this.popoverService.hasPopover(trigger)) {
|
||||||
|
this.popoverService.hidePopover(trigger);
|
||||||
|
} else {
|
||||||
|
const ctx: any = {
|
||||||
|
disabled: !(this.isAdd || this.isEdit),
|
||||||
|
isLatest: isLatest,
|
||||||
|
releaseNotes: isLatest
|
||||||
|
? this.entityForm.get('versionInfo.latestVersionReleaseNotes').value
|
||||||
|
: this.entityForm.get('versionInfo.minVersionReleaseNotes').value
|
||||||
|
};
|
||||||
|
const releaseNotesPanelPopover = this.popoverService.displayPopover(trigger, this.renderer,
|
||||||
|
this.viewContainerRef, ReleaseNotesPanelComponent, ['leftOnly', 'leftBottomOnly', 'leftTopOnly'], true, null,
|
||||||
|
ctx,
|
||||||
|
{},
|
||||||
|
{}, {}, false, () => {}, {padding: '16px 24px'});
|
||||||
|
releaseNotesPanelPopover.tbComponentRef.instance.popover = releaseNotesPanelPopover;
|
||||||
|
releaseNotesPanelPopover.tbComponentRef.instance.releaseNotesApplied.subscribe((releaseNotes) => {
|
||||||
|
releaseNotesPanelPopover.hide();
|
||||||
|
if (isLatest) {
|
||||||
|
this.entityForm.get('versionInfo.latestVersionReleaseNotes').setValue(releaseNotes);
|
||||||
|
this.entityForm.get('versionInfo.latestVersionReleaseNotes').markAsDirty();
|
||||||
|
} else {
|
||||||
|
this.entityForm.get('versionInfo.minVersionReleaseNotes').setValue(releaseNotes);
|
||||||
|
this.entityForm.get('versionInfo.minVersionReleaseNotes').markAsDirty();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private base64Format(control: UntypedFormControl): { [key: string]: boolean } | null {
|
private base64Format(control: UntypedFormControl): { [key: string]: boolean } | null {
|
||||||
if (control.value === '') {
|
if (control.value === '') {
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
@ -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.
|
||||||
|
|
||||||
|
-->
|
||||||
|
<div class="tb-release-notes-panel">
|
||||||
|
<div class="tb-release-notes-title">{{ title | translate }}</div>
|
||||||
|
<editor [init]="tinyMceOptions" [formControl]="releaseNotesControl"></editor>
|
||||||
|
<div class="tb-release-notes-panel-buttons">
|
||||||
|
<button mat-button
|
||||||
|
color="primary"
|
||||||
|
type="button"
|
||||||
|
(click)="cancel()">
|
||||||
|
{{ 'action.cancel' | translate }}
|
||||||
|
</button>
|
||||||
|
<button mat-raised-button
|
||||||
|
color="primary"
|
||||||
|
type="button"
|
||||||
|
(click)="apply()"
|
||||||
|
[disabled]="releaseNotesControl.invalid || !releaseNotesControl.dirty">
|
||||||
|
{{ 'action.apply' | translate }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@ -0,0 +1,41 @@
|
|||||||
|
/**
|
||||||
|
* 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 '../../../../../../scss/constants';
|
||||||
|
|
||||||
|
.tb-release-notes-panel {
|
||||||
|
width: 600px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 16px;
|
||||||
|
@media #{$mat-lt-md} {
|
||||||
|
width: 90vw;
|
||||||
|
}
|
||||||
|
.tb-release-notes-title {
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 500;
|
||||||
|
line-height: 24px;
|
||||||
|
letter-spacing: 0.25px;
|
||||||
|
color: rgba(0, 0, 0, 0.87);
|
||||||
|
}
|
||||||
|
.tb-release-notes-panel-buttons {
|
||||||
|
height: 40px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
gap: 16px;
|
||||||
|
justify-content: flex-end;
|
||||||
|
align-items: flex-end;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,82 @@
|
|||||||
|
///
|
||||||
|
/// 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, EventEmitter, Input, OnInit, Output, ViewEncapsulation } from '@angular/core';
|
||||||
|
import { FormBuilder, FormControl } from '@angular/forms';
|
||||||
|
import { TbPopoverComponent } from '@shared/components/popover.component';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'tb-release-notes-panel',
|
||||||
|
templateUrl: './release-notes-panel.component.html',
|
||||||
|
styleUrls: ['./release-notes-panel.component.scss'],
|
||||||
|
encapsulation: ViewEncapsulation.None
|
||||||
|
})
|
||||||
|
export class ReleaseNotesPanelComponent implements OnInit {
|
||||||
|
|
||||||
|
@Input()
|
||||||
|
disabled: boolean;
|
||||||
|
|
||||||
|
@Input()
|
||||||
|
releaseNotes: string;
|
||||||
|
|
||||||
|
@Input()
|
||||||
|
isLatest: boolean;
|
||||||
|
|
||||||
|
@Input()
|
||||||
|
popover: TbPopoverComponent<ReleaseNotesPanelComponent>;
|
||||||
|
|
||||||
|
@Output()
|
||||||
|
releaseNotesApplied = new EventEmitter<string>();
|
||||||
|
|
||||||
|
title: string;
|
||||||
|
|
||||||
|
releaseNotesControl: FormControl<string>;
|
||||||
|
|
||||||
|
tinyMceOptions: Record<string, any> = {
|
||||||
|
base_url: '/assets/tinymce',
|
||||||
|
suffix: '.min',
|
||||||
|
plugins: ['lists'],
|
||||||
|
menubar: 'edit insert view format',
|
||||||
|
toolbar: ['fontfamily fontsize | bold italic underline strikethrough forecolor backcolor',
|
||||||
|
'alignleft aligncenter alignright alignjustify | bullist'],
|
||||||
|
toolbar_mode: 'sliding',
|
||||||
|
height: 400,
|
||||||
|
autofocus: false,
|
||||||
|
branding: false,
|
||||||
|
promotion: false
|
||||||
|
};
|
||||||
|
|
||||||
|
constructor(private fb: FormBuilder) {
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
this.releaseNotesControl = this.fb.control(this.releaseNotes);
|
||||||
|
this.title = this.isLatest ? 'mobile.latest-version-release-notes' : 'mobile.min-version-release-notes';
|
||||||
|
if (this.disabled) {
|
||||||
|
this.releaseNotesControl.disable({emitEvent: false});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cancel() {
|
||||||
|
this.popover?.hide();
|
||||||
|
}
|
||||||
|
|
||||||
|
apply() {
|
||||||
|
if (this.releaseNotesControl.valid) {
|
||||||
|
this.releaseNotesApplied.emit(this.releaseNotesControl.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -4067,6 +4067,7 @@
|
|||||||
"application-details": "Application details",
|
"application-details": "Application details",
|
||||||
"application-package": "Application Package",
|
"application-package": "Application Package",
|
||||||
"application-secret": "Application Secret",
|
"application-secret": "Application Secret",
|
||||||
|
"application-secret-required": "Application Secret is required",
|
||||||
"applications": "Applications",
|
"applications": "Applications",
|
||||||
"copy-app-id": "Copy App ID",
|
"copy-app-id": "Copy App ID",
|
||||||
"copy-app-store-link": "Copy App Store link",
|
"copy-app-store-link": "Copy App Store link",
|
||||||
@ -4100,7 +4101,9 @@
|
|||||||
"suspended": "Suspended"
|
"suspended": "Suspended"
|
||||||
},
|
},
|
||||||
"store-information": "Store information",
|
"store-information": "Store information",
|
||||||
"version-information": "Version information"
|
"version-information": "Version information",
|
||||||
|
"min-version-release-notes": "Min version release notes",
|
||||||
|
"latest-version-release-notes": "Latest version release notes"
|
||||||
},
|
},
|
||||||
"notification": {
|
"notification": {
|
||||||
"action-button": "Action button",
|
"action-button": "Action button",
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user