UI: Add release note to mobile application

This commit is contained in:
Vladyslav_Prykhodko 2024-10-16 10:54:26 +03:00
parent 119354a441
commit ed24383e2a
8 changed files with 247 additions and 15 deletions

View File

@ -21,11 +21,13 @@ import { CommonModule } from '@angular/common';
import { SharedModule } from '@shared/shared.module';
import { HomeComponentsModule } from '@home/components/home-components.module';
import { ApplicationsRoutingModule } from '@home/pages/mobile/applications/applications-routing.module';
import { ReleaseNotesPanelComponent } from '@home/pages/mobile/applications/release-notes-panel.component';
@NgModule({
declarations: [
MobileAppComponent,
MobileAppTableHeaderComponent
MobileAppTableHeaderComponent,
ReleaseNotesPanelComponent
],
imports: [
CommonModule,

View File

@ -120,7 +120,7 @@ export class MobileAppTableConfigResolver {
backgroundColor = 'rgba(209, 39, 48, 0.06)';
break;
case MobileAppStatus.DRAFT:
backgroundColor = 'rgba(160, 160, 160, 0.06)';
backgroundColor = 'rgba(0, 148, 255, 0.06)';
break;
}
return `<div style="border-radius: 14px; height: 28px; line-height: 20px; padding: 4px 10px;
@ -142,7 +142,7 @@ export class MobileAppTableConfigResolver {
styleObj.color = '#D12730';
break;
case MobileAppStatus.DRAFT:
styleObj.color = '#A0A0A0';
styleObj.color = '#0094FF';
break;
}
return styleObj;

View File

@ -68,6 +68,9 @@
</tb-copy-button>
</div>
<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 appearance="outline">
<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;">
<div class="tb-form-panel-title" translate>mobile.version-information</div>
<section fxLayout="column">
<mat-form-field appearance="outline">
<mat-label translate>mobile.min-version</mat-label>
<input matInput formControlName="minVersion">
</mat-form-field>
<mat-form-field appearance="outline">
<mat-label translate>mobile.latest-version</mat-label>
<input matInput formControlName="latestVersion">
</mat-form-field>
<section fxLayout="row" fxLayoutAlign="start start">
<mat-form-field fxFlex appearance="outline">
<mat-label translate>mobile.min-version</mat-label>
<input matInput formControlName="minVersion">
</mat-form-field>
<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>
<input matInput formControlName="latestVersion">
</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 class="tb-form-panel stroked no-padding-bottom" formGroupName="storeInfo">

View File

@ -14,7 +14,7 @@
/// 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 { AppState } from '@core/core.state';
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 { PlatformType, platformTypeTranslations } from '@shared/models/oauth2.models';
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({
selector: 'tb-mobile-app',
@ -51,7 +54,10 @@ export class MobileAppComponent extends EntityComponent<MobileApp> {
@Inject('entity') protected entityValue: MobileApp,
@Inject('entitiesTableConfig') protected entitiesTableConfigValue: EntityTableConfig<MobileApp>,
protected cd: ChangeDetectorRef,
public fb: FormBuilder) {
public fb: FormBuilder,
private popoverService: TbPopoverService,
private renderer: Renderer2,
private viewContainerRef: ViewContainerRef) {
super(store, fb, entityValue, entitiesTableConfigValue, cd);
}
@ -64,7 +70,9 @@ export class MobileAppComponent extends EntityComponent<MobileApp> {
status: [entity?.status ? entity.status : MobileAppStatus.DRAFT],
versionInfo: this.fb.group({
minVersion: [entity?.versionInfo?.minVersion ? entity.versionInfo.minVersion : ''],
minVersionReleaseNotes: [entity?.versionInfo?.minVersionReleaseNotes ? entity.versionInfo.minVersionReleaseNotes : ''],
latestVersion: [entity?.versionInfo?.latestVersion ? entity.versionInfo.latestVersion : ''],
latestVersionReleaseNotes: [entity?.versionInfo?.latestVersionReleaseNotes ? entity.versionInfo.latestVersionReleaseNotes : ''],
}),
storeInfo: this.fb.group({
storeLink: [entity?.storeInfo?.storeLink ? entity.storeInfo.storeLink : ''],
@ -89,7 +97,7 @@ export class MobileAppComponent extends EntityComponent<MobileApp> {
form.get('status').valueChanges.pipe(
takeUntilDestroyed()
).subscribe((value: MobileAppStatus) => {
if (value === MobileAppStatus.PUBLISHED) {
if (value !== MobileAppStatus.DRAFT) {
form.get('storeInfo.storeLink').addValidators(Validators.required);
form.get('storeInfo.sha256CertFingerprints').addValidators(Validators.required);
form.get('storeInfo.appId').addValidators(Validators.required);
@ -113,6 +121,7 @@ export class MobileAppComponent extends EntityComponent<MobileApp> {
override updateFormState(): void {
super.updateFormState();
if (this.isEdit && this.entityForm && !this.isAdd) {
this.entityForm.get('status').updateValueAndValidity({onlySelf: false});
this.entityForm.get('platformType').disable({emitEvent: false});
if (this.entityForm.get('platformType').value === PlatformType.ANDROID) {
this.entityForm.get('storeInfo.appId').disable({emitEvent: false});
@ -136,6 +145,40 @@ export class MobileAppComponent extends EntityComponent<MobileApp> {
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 {
if (control.value === '') {
return null;

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.
-->
<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>

View File

@ -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;
}
}

View File

@ -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);
}
}
}

View File

@ -4067,6 +4067,7 @@
"application-details": "Application details",
"application-package": "Application Package",
"application-secret": "Application Secret",
"application-secret-required": "Application Secret is required",
"applications": "Applications",
"copy-app-id": "Copy App ID",
"copy-app-store-link": "Copy App Store link",
@ -4100,7 +4101,9 @@
"suspended": "Suspended"
},
"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": {
"action-button": "Action button",