UI: added separate mobile qrcode widget to cards bundle

This commit is contained in:
rusikv 2024-05-07 11:08:39 +03:00
parent 4ee969fa1e
commit 953c215002
17 changed files with 415 additions and 36 deletions

View File

@ -17,6 +17,7 @@
"cards.label_widget", "cards.label_widget",
"cards.dashboard_state_widget", "cards.dashboard_state_widget",
"cards.qr_code", "cards.qr_code",
"cards.mobile_app_qr_code",
"cards.attributes_card", "cards.attributes_card",
"cards.html_card", "cards.html_card",
"cards.html_value_card", "cards.html_value_card",

View File

@ -14,6 +14,6 @@
"home_page_widgets.documentation_links", "home_page_widgets.documentation_links",
"home_page_widgets.dashboards", "home_page_widgets.dashboards",
"home_page_widgets.usage_info", "home_page_widgets.usage_info",
"home_page_widgets.mobile_app_qr_code" "home_page_widgets.home_mobile_app_qr_code"
] ]
} }

View File

@ -0,0 +1,21 @@
{
"fqn": "home_page_widgets.home_mobile_app_qr_code",
"name": "Mobile app QR code",
"deprecated": false,
"image": null,
"description": null,
"descriptor": {
"type": "static",
"sizeX": 6,
"sizeY": 3,
"resources": [],
"templateHtml": "<tb-mobile-app-qrcode-widget>\n</tb-mobile-app-qrcode-widget>",
"templateCss": "",
"controllerScript": "self.onInit = function() {\n}",
"settingsSchema": "",
"dataKeySettingsSchema": "",
"settingsDirective": "",
"defaultConfig": "{\"datasources\":[{\"type\":\"static\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Random\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.15479322438769105,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -1000) {\\n\\tvalue = -1000;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":false,\"backgroundColor\":\"rgb(255, 255, 255)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{\"cardHtml\":\"<div class='card'>HTML code here</div>\",\"cardCss\":\".card {\\n font-weight: bold;\\n font-size: 32px;\\n color: #999;\\n width: 100%;\\n height: 100%;\\n display: flex;\\n align-items: center;\\n justify-content: center;\\n}\"},\"title\":\"Mobile app QR code\",\"dropShadow\":true}"
},
"tags": null
}

View File

@ -1,21 +1,21 @@
{ {
"fqn": "home_page_widgets.mobile_app_qr_code", "fqn": "cards.mobile_app_qr_code",
"name": "Mobile app QR code", "name": "Mobile app QR code",
"deprecated": false, "deprecated": false,
"image": null, "image": null,
"description": null, "description": null,
"descriptor": { "descriptor": {
"type": "static", "type": "static",
"sizeX": 6, "sizeX": 7.5,
"sizeY": 3, "sizeY": 3,
"resources": [], "resources": [],
"templateHtml": "<tb-mobile-app-qrcode-widget\n [ctx]=\"ctx\">\n</tb-mobile-app-qrcode-widget>", "templateHtml": "<tb-mobile-app-qrcode-widget\n [ctx]=\"ctx\">\n</tb-mobile-app-qrcode-widget>",
"templateCss": "", "templateCss": "",
"controllerScript": "self.onInit = function() {\n self.ctx.$scope.mobileAppQrcodeWidget.ngOnInit();\n}\n\nself.onDataUpdated = function() {\n self.ctx.$scope.mobileAppQrcodeWidget.ngOnDestroy();\n}", "controllerScript": "self.onInit = function() {\n}\n",
"settingsSchema": "", "settingsSchema": "",
"dataKeySettingsSchema": "", "dataKeySettingsSchema": "",
"settingsDirective": "", "settingsDirective": "tb-mobile-app-qr-code-widget-settings",
"defaultConfig": "{\"datasources\":[{\"type\":\"static\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Random\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.15479322438769105,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -1000) {\\n\\tvalue = -1000;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":false,\"backgroundColor\":\"rgb(255, 255, 255)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{\"cardHtml\":\"<div class='card'>HTML code here</div>\",\"cardCss\":\".card {\\n font-weight: bold;\\n font-size: 32px;\\n color: #999;\\n width: 100%;\\n height: 100%;\\n display: flex;\\n align-items: center;\\n justify-content: center;\\n}\"},\"title\":\"HTML Card\",\"dropShadow\":true}" "defaultConfig": "{\"datasources\":[{\"type\":\"static\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Random\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.15479322438769105,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -1000) {\\n\\tvalue = -1000;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":true,\"backgroundColor\":\"rgb(255, 255, 255)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{\"useDefaultApp\":true,\"androidConfig\":{\"enabled\":true,\"appPackage\":\"\",\"sha256CertFingerprints\":\"\"},\"iosConfig\":{\"enabled\":true,\"appId\":\"\"},\"qrCodeConfig\":{\"badgeEnabled\":true,\"badgeStyle\":\"ORIGINAL\",\"badgePosition\":\"RIGHT\",\"qrCodeLabelEnabled\":true,\"qrCodeLabel\":\"Scan to connect or download mobile app\"}},\"title\":\"Mobile app QR code\",\"dropShadow\":true,\"enableFullscreen\":false,\"widgetStyle\":{},\"widgetCss\":\"\",\"pageSize\":1024,\"noDataDisplayMessage\":\"\",\"showTitleIcon\":false,\"titleTooltip\":\"\",\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400}}"
}, },
"tags": null "tags": null
} }

View File

@ -18,7 +18,7 @@ import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { defaultHttpOptionsFromConfig, RequestConfig } from '@core/http/http-utils'; import { defaultHttpOptionsFromConfig, RequestConfig } from '@core/http/http-utils';
import { Observable } from 'rxjs'; import { Observable } from 'rxjs';
import { MobileAppSettings } from '@shared/models/mobile-app.models'; import { MobileAppQRCodeSettings } from '@shared/models/mobile-app.models';
@Injectable({ @Injectable({
providedIn: 'root' providedIn: 'root'
@ -30,12 +30,12 @@ export class MobileAppService {
) { ) {
} }
public getMobileAppSettings(config?: RequestConfig): Observable<MobileAppSettings> { public getMobileAppSettings(config?: RequestConfig): Observable<MobileAppQRCodeSettings> {
return this.http.get<MobileAppSettings>(`/api/mobile/app/settings`, defaultHttpOptionsFromConfig(config)); return this.http.get<MobileAppQRCodeSettings>(`/api/mobile/app/settings`, defaultHttpOptionsFromConfig(config));
} }
public saveMobileAppSettings(mobileAppSettings: MobileAppSettings, config?: RequestConfig): Observable<MobileAppSettings> { public saveMobileAppSettings(mobileAppSettings: MobileAppQRCodeSettings, config?: RequestConfig): Observable<MobileAppQRCodeSettings> {
return this.http.post<MobileAppSettings>(`/api/mobile/app/settings`, mobileAppSettings, defaultHttpOptionsFromConfig(config)); return this.http.post<MobileAppQRCodeSettings>(`/api/mobile/app/settings`, mobileAppSettings, defaultHttpOptionsFromConfig(config));
} }
public getMobileAppDeepLink( config?: RequestConfig): Observable<string> { public getMobileAppDeepLink( config?: RequestConfig): Observable<string> {

View File

@ -0,0 +1,50 @@
///
/// 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 {
AndroidConfig,
BadgePosition,
BadgeStyle,
IosConfig,
QRCodeConfig
} from '@shared/models/mobile-app.models';
export type MobileAppQrCodeWidgetSettings = {
useDefaultApp: boolean;
androidConfig: AndroidConfig;
iosConfig: IosConfig;
qrCodeConfig: Omit<QRCodeConfig, 'showOnHomePage'>;
}
export const mobileAppQrCodeWidgetDefaultSettings: MobileAppQrCodeWidgetSettings = {
useDefaultApp: true,
androidConfig: {
enabled: true,
appPackage: '',
sha256CertFingerprints: ''
},
iosConfig: {
enabled: true,
appId: ''
},
qrCodeConfig: {
badgeEnabled: true,
badgeStyle: BadgeStyle.ORIGINAL,
badgePosition: BadgePosition.RIGHT,
qrCodeLabelEnabled: true,
qrCodeLabel: 'Scan to connect or download mobile app'
}
}

View File

@ -15,15 +15,15 @@
limitations under the License. limitations under the License.
--> -->
<div *ngIf="!previewMode" class="tb-title" translate>admin.mobile-app.connect-mobile-app</div> <div *ngIf="!previewMode && !ctx" class="tb-title" translate>admin.mobile-app.connect-mobile-app</div>
<div class="tb-flex column center align-center"> <div class="tb-flex column center align-center">
<div class="tb-flex row align-center shrink" style="margin-bottom: 8px" <div class="tb-flex row align-center shrink"
[class.row]="mobileAppSettings?.qrCodeConfig.badgePosition === badgePosition.RIGHT" [class.row]="mobileAppSettings?.qrCodeConfig.badgePosition === badgePosition.RIGHT"
[class.row-reverse]="mobileAppSettings?.qrCodeConfig.badgePosition === badgePosition.LEFT"> [class.row-reverse]="mobileAppSettings?.qrCodeConfig.badgePosition === badgePosition.LEFT">
<canvas #canvas class="tb-qrcode"></canvas> <canvas #canvas class="tb-qrcode"></canvas>
<div *ngIf="mobileAppSettings?.qrCodeConfig.badgeEnabled" <div *ngIf="mobileAppSettings?.qrCodeConfig.badgeEnabled"
class="tb-flex column no-gap tb-badge-container" class="tb-flex column no-gap tb-badge-container"
[class.tb-badge-container]="!previewMode"> [class.tb-badge-container]="!previewMode && !ctx">
<ng-container *ngIf="mobileAppSettings.qrCodeConfig.badgeStyle === badgeStyle.ORIGINAL;else white"> <ng-container *ngIf="mobileAppSettings.qrCodeConfig.badgeStyle === badgeStyle.ORIGINAL;else white">
<img *ngIf="mobileAppSettings.iosConfig.enabled" [src]="badgeStyleURLMap.get(badgeStyle.ORIGINAL).iOS"> <img *ngIf="mobileAppSettings.iosConfig.enabled" [src]="badgeStyleURLMap.get(badgeStyle.ORIGINAL).iOS">
<img *ngIf="mobileAppSettings.androidConfig.enabled" [src]="badgeStyleURLMap.get(badgeStyle.ORIGINAL).android"> <img *ngIf="mobileAppSettings.androidConfig.enabled" [src]="badgeStyleURLMap.get(badgeStyle.ORIGINAL).android">

View File

@ -17,7 +17,6 @@
@import '../../../../../../scss/constants'; @import '../../../../../../scss/constants';
:host { :host {
width: 100%;
height: 100%; height: 100%;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
@ -25,12 +24,14 @@
} }
.tb-title { .tb-title {
padding-bottom: 12px;
align-self: start; align-self: start;
font-weight: 600; font-weight: 600;
font-size: 20px; font-size: 20px;
line-height: 24px; line-height: 24px;
letter-spacing: 0.1px; letter-spacing: 0.1px;
color: rgba(0, 0, 0, 0.76); color: rgba(0, 0, 0, 0.76);
@media #{$mat-md-lg} { @media #{$mat-md-lg} {
font-weight: 500; font-weight: 500;
font-size: 14px; font-size: 14px;

View File

@ -18,13 +18,14 @@ import { AfterViewInit, ChangeDetectorRef, Component, ElementRef, Input, OnDestr
import { PageComponent } from '@shared/components/page.component'; import { PageComponent } from '@shared/components/page.component';
import { AppState } from '@core/core.state'; import { AppState } from '@core/core.state';
import { Store } from '@ngrx/store'; import { Store } from '@ngrx/store';
import { BadgePosition, BadgeStyle, badgeStyleURLMap, MobileAppSettings } from '@shared/models/mobile-app.models'; import { BadgePosition, BadgeStyle, badgeStyleURLMap, MobileAppQRCodeSettings } from '@shared/models/mobile-app.models';
import { MobileAppService } from '@core/http/mobile-app.service'; import { MobileAppService } from '@core/http/mobile-app.service';
import { WidgetContext } from '@home/models/widget-component.models'; import { WidgetContext } from '@home/models/widget-component.models';
import { UtilsService } from '@core/services/utils.service'; import { UtilsService } from '@core/services/utils.service';
import { interval, mergeMap, Observable, Subject, takeUntil } from 'rxjs'; import { interval, mergeMap, Observable, Subject, takeUntil } from 'rxjs';
import { MINUTE } from '@shared/models/time/time.models'; import { MINUTE } from '@shared/models/time/time.models';
import { coerceBoolean } from '@shared/decorators/coercion'; import { coerceBoolean } from '@shared/decorators/coercion';
import { MobileAppQrCodeWidgetSettings } from '@home/components/widget/lib/cards/mobile-app-qr-code-widge.models';
@Component({ @Component({
selector: 'tb-mobile-app-qrcode-widget', selector: 'tb-mobile-app-qrcode-widget',
@ -41,7 +42,7 @@ export class MobileAppQrcodeWidgetComponent extends PageComponent implements OnI
previewMode: boolean; previewMode: boolean;
@Input() @Input()
set mobileAppSettings(settings: MobileAppSettings) { set mobileAppSettings(settings: MobileAppQRCodeSettings | MobileAppQrCodeWidgetSettings) {
if (settings) { if (settings) {
this.mobileAppSettingsValue = settings; this.mobileAppSettingsValue = settings;
} }
@ -59,7 +60,7 @@ export class MobileAppQrcodeWidgetComponent extends PageComponent implements OnI
badgePosition = BadgePosition; badgePosition = BadgePosition;
badgeStyleURLMap = badgeStyleURLMap; badgeStyleURLMap = badgeStyleURLMap;
private mobileAppSettingsValue: MobileAppSettings; private mobileAppSettingsValue: MobileAppQRCodeSettings | MobileAppQrCodeWidgetSettings;
private deepLinkTTL: number; private deepLinkTTL: number;
constructor(protected store: Store<AppState>, constructor(protected store: Store<AppState>,
@ -71,9 +72,8 @@ export class MobileAppQrcodeWidgetComponent extends PageComponent implements OnI
ngOnInit(): void { ngOnInit(): void {
if (this.ctx) { if (this.ctx) {
this.ctx.$scope.mobileAppQrcodeWidget = this; this.mobileAppSettings = this.ctx.settings;
} } else {
if (!this.mobileAppSettings) {
this.mobileAppService.getMobileAppSettings().subscribe((settings => { this.mobileAppService.getMobileAppSettings().subscribe((settings => {
this.mobileAppSettings = settings; this.mobileAppSettings = settings;
this.cd.detectChanges(); this.cd.detectChanges();
@ -104,7 +104,7 @@ export class MobileAppQrcodeWidgetComponent extends PageComponent implements OnI
updateQRCode(link: string) { updateQRCode(link: string) {
import('qrcode').then((QRCode) => { import('qrcode').then((QRCode) => {
QRCode.toCanvas(this.canvasRef.nativeElement, link, { width: 125}); QRCode.toCanvas(this.canvasRef.nativeElement, link, { width: 90 });
}); });
} }

View File

@ -0,0 +1,130 @@
<!--
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.
-->
<section class="tb-widget-settings tb-form-panel no-border no-padding" [formGroup]="mobileAppQRCodeWidgetSettingsForm" fxLayout="column">
<div class="tb-form-panel">
<div fxLayout="row" fxLayoutAlign="space-between center">
<div class="tb-form-panel-title" translate>admin.mobile-app.applications</div>
<tb-toggle-select formControlName="useDefaultApp">
<tb-toggle-option [value]="true">{{ 'admin.mobile-app.default' | translate }}</tb-toggle-option>
<tb-toggle-option [value]="false">{{ 'admin.mobile-app.custom' | translate }}</tb-toggle-option>
</tb-toggle-select>
</div>
<div class="tb-form-panel stroked no-padding no-gap" formGroupName="androidConfig">
<div class="tb-form-row no-border no-padding-bottom">
<mat-slide-toggle class="mat-slide" formControlName="enabled" (click)="$event.stopPropagation()">
{{ 'admin.mobile-app.android' | translate }}
</mat-slide-toggle>
</div>
<div class="tb-form-panel no-border no-padding-top"
*ngIf="!mobileAppQRCodeWidgetSettingsForm.get('useDefaultApp').value && mobileAppQRCodeWidgetSettingsForm.get('androidConfig.enabled').value">
<div class="tb-form-row column-xs">
<div class="tb-fixed-width">{{ 'admin.mobile-app.app-package-name' | translate }}</div>
<mat-form-field class="flex" appearance="outline" subscriptSizing="dynamic">
<input matInput formControlName="appPackage" placeholder="{{'admin.mobile-app.set' | translate}}">
<mat-icon matSuffix
matTooltipPosition="above"
matTooltipClass="tb-error-tooltip"
[matTooltip]="'admin.mobile-app.app-package-name-required' | translate"
*ngIf="mobileAppQRCodeWidgetSettingsForm.get('androidConfig.appPackage').hasError('required')
&& mobileAppQRCodeWidgetSettingsForm.get('androidConfig.appPackage').touched"
class="tb-error">
warning
</mat-icon>
</mat-form-field>
</div>
</div>
<div class="tb-form-panel no-border no-padding-top"
*ngIf="!mobileAppQRCodeWidgetSettingsForm.get('useDefaultApp').value && mobileAppQRCodeWidgetSettingsForm.get('androidConfig.enabled').value">
<div class="tb-form-row column-xs">
<div class="tb-fixed-width">{{ 'admin.mobile-app.sha256-certificate-fingerprints' | translate }}</div>
<mat-form-field class="flex" appearance="outline" subscriptSizing="dynamic">
<input matInput formControlName="sha256CertFingerprints" placeholder="{{'admin.mobile-app.set' | translate}}">
<mat-icon matSuffix
matTooltipPosition="above"
matTooltipClass="tb-error-tooltip"
[matTooltip]="'admin.mobile-app.sha256-certificate-fingerprints-required' | translate"
*ngIf="mobileAppQRCodeWidgetSettingsForm.get('androidConfig.sha256CertFingerprints').hasError('required')
&& mobileAppQRCodeWidgetSettingsForm.get('androidConfig.sha256CertFingerprints').touched"
class="tb-error">
warning
</mat-icon>
</mat-form-field>
</div>
</div>
</div>
<div class="tb-form-panel stroked no-padding no-gap" formGroupName="iosConfig">
<div class="tb-form-row no-border no-padding-bottom">
<mat-slide-toggle class="mat-slide" formControlName="enabled" (click)="$event.stopPropagation()">
{{ 'admin.mobile-app.ios' | translate }}
</mat-slide-toggle>
</div>
<div class="tb-form-panel no-border no-padding-top"
*ngIf="!mobileAppQRCodeWidgetSettingsForm.get('useDefaultApp').value && mobileAppQRCodeWidgetSettingsForm.get('iosConfig.enabled').value">
<div class="tb-form-row column-xs">
<div class="tb-fixed-width">{{ 'admin.mobile-app.app-id' | translate }}</div>
<mat-form-field class="flex" appearance="outline" subscriptSizing="dynamic">
<input matInput formControlName="appId" placeholder="{{'admin.mobile-app.set' | translate}}">
<mat-icon matSuffix
matTooltipPosition="above"
matTooltipClass="tb-error-tooltip"
[matTooltip]="'admin.mobile-app.app-id-required' | translate"
*ngIf="mobileAppQRCodeWidgetSettingsForm.get('iosConfig.appId').hasError('required')
&& mobileAppQRCodeWidgetSettingsForm.get('iosConfig.appId').touched"
class="tb-error">
warning
</mat-icon>
</mat-form-field>
</div>
</div>
</div>
</div>
<div class="tb-form-panel" formGroupName="qrCodeConfig">
<div class="tb-form-panel stroked no-padding no-gap">
<div class="tb-form-row space-between no-border no-padding-bottom column-xs">
<mat-slide-toggle class="mat-slide tb-fixed-width" formControlName="badgeEnabled"
(click)="$event.stopPropagation()">
{{ 'admin.mobile-app.badges' | translate }}
</mat-slide-toggle>
<mat-form-field class="flex" appearance="outline" subscriptSizing="dynamic">
<mat-select formControlName="badgeStyle" appearance="outline" subscriptSizing="dynamic">
<mat-option *ngFor="let badgeStyle of badgeStyleTranslationsMap | keyvalue" [value]="badgeStyle.key">
{{ badgeStyle.value | translate }}
</mat-option>
</mat-select>
</mat-form-field>
<mat-form-field class="flex" appearance="outline" subscriptSizing="dynamic">
<mat-select formControlName="badgePosition" appearance="outline" subscriptSizing="dynamic">
<mat-option *ngFor="let badgePosition of badgePositionTranslationsMap | keyvalue" [value]="badgePosition.key">
{{ badgePosition.value | translate }}
</mat-option>
</mat-select>
</mat-form-field>
</div>
</div>
<div class="tb-form-panel stroked no-padding no-gap">
<div class="tb-form-row space-between no-border no-padding-bottom column-xs">
<mat-slide-toggle class="mat-slide tb-fixed-width" formControlName="qrCodeLabelEnabled" (click)="$event.stopPropagation()">
{{ 'admin.mobile-app.label' | translate }}
</mat-slide-toggle>
<mat-form-field class="flex" appearance="outline" subscriptSizing="dynamic">
<input matInput formControlName="qrCodeLabel" maxlength="50" placeholder="{{'admin.mobile-app.set' | translate}}">
</mat-form-field>
</div>
</div>
</div>
</section>

View File

@ -0,0 +1,19 @@
/**
* 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.
*/
.tb-fixed-width {
min-width: 230px;
}

View File

@ -0,0 +1,156 @@
///
/// 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 } from "@angular/core";
import { UntypedFormBuilder, UntypedFormGroup, Validators } from "@angular/forms";
import { WidgetSettings, WidgetSettingsComponent } from "@shared/models/widget.models";
import { AppState } from '@core/core.state';
import { Store } from "@ngrx/store";
import { badgePositionTranslationsMap, badgeStyleTranslationsMap } from '@shared/models/mobile-app.models';
import { mobileAppQrCodeWidgetDefaultSettings } from '@home/components/widget/lib/cards/mobile-app-qr-code-widge.models';
@Component({
selector: 'tb-mobile-app-qr-code-widget-settings',
templateUrl: './mobile-app-qr-code-widget-settings.component.html',
styleUrls: ['/mobile-app-qr-code-widget-settings.component.scss', './../widget-settings.scss']
})
export class MobileAppQrCodeWidgetSettingsComponent extends WidgetSettingsComponent {
mobileAppQRCodeWidgetSettingsForm: UntypedFormGroup;
badgePositionTranslationsMap = badgePositionTranslationsMap;
badgeStyleTranslationsMap = badgeStyleTranslationsMap;
constructor(protected store: Store<AppState>,
private fb: UntypedFormBuilder) {
super(store);
}
protected settingsForm(): UntypedFormGroup {
return this.mobileAppQRCodeWidgetSettingsForm;
}
protected defaultSettings(): WidgetSettings {
return {...mobileAppQrCodeWidgetDefaultSettings};
}
protected onSettingsSet(settings: WidgetSettings) {
this.mobileAppQRCodeWidgetSettingsForm = this.fb.group({
useDefaultApp: [settings.useDefaultApp, []],
androidConfig: this.fb.group({
enabled: [settings.androidConfig.enabled, []],
appPackage: [settings.androidConfig.appPackage, []],
sha256CertFingerprints: [settings.androidConfig.sha256CertFingerprints, []]
}),
iosConfig: this.fb.group({
enabled: [settings.iosConfig.enabled, []],
appId: [settings.iosConfig.appId, []]
}),
qrCodeConfig: this.fb.group({
badgeEnabled: [settings.qrCodeConfig.badgeEnabled, []],
badgeStyle: [{value: settings.qrCodeConfig.badgeStyle, disabled: true}, []],
badgePosition: [{value: settings.qrCodeConfig.badgePosition, disabled: true}, []],
qrCodeLabelEnabled: [settings.qrCodeConfig.qrCodeLabelEnabled, []],
qrCodeLabel: [settings.qrCodeConfig.qrCodeLabel, []]
})
});
}
protected validatorTriggers(): string[] {
return ['useDefaultApp', 'androidConfig.enabled', 'iosConfig.enabled', 'qrCodeConfig.badgeEnabled', 'qrCodeConfig.qrCodeLabelEnabled'];
}
protected updateValidators(emitEvent: boolean) {
const useDefaultApp = this.mobileAppQRCodeWidgetSettingsForm.get('useDefaultApp').value;
const androidEnabled = this.mobileAppQRCodeWidgetSettingsForm.get('androidConfig.enabled').value;
const iosEnabled = this.mobileAppQRCodeWidgetSettingsForm.get('iosConfig.enabled').value;
const badgeEnabled = this.mobileAppQRCodeWidgetSettingsForm.get('qrCodeConfig.badgeEnabled').value;
const qrCodeLabelEnabled = this.mobileAppQRCodeWidgetSettingsForm.get('qrCodeConfig.qrCodeLabelEnabled').value;
if (useDefaultApp) {
this.mobileAppQRCodeWidgetSettingsForm.get('androidConfig.appPackage').clearValidators();
this.mobileAppQRCodeWidgetSettingsForm.get('androidConfig.sha256CertFingerprints').clearValidators();
this.mobileAppQRCodeWidgetSettingsForm.get('iosConfig.appId').clearValidators();
} else {
this.mobileAppQRCodeWidgetSettingsForm.get('androidConfig.appPackage').setValidators([Validators.required]);
this.mobileAppQRCodeWidgetSettingsForm.get('androidConfig.sha256CertFingerprints').setValidators([Validators.required]);
this.mobileAppQRCodeWidgetSettingsForm.get('iosConfig.appId').setValidators([Validators.required]);
}
if (androidEnabled) {
if (!useDefaultApp) {
this.mobileAppQRCodeWidgetSettingsForm.get('androidConfig.appPackage').setValidators([Validators.required]);
this.mobileAppQRCodeWidgetSettingsForm.get('androidConfig.sha256CertFingerprints').setValidators([Validators.required]);
}
this.mobileAppQRCodeWidgetSettingsForm.get('qrCodeConfig.badgeEnabled').enable({emitEvent: false});
if (badgeEnabled) {
this.mobileAppQRCodeWidgetSettingsForm.get('qrCodeConfig.badgeStyle').enable();
this.mobileAppQRCodeWidgetSettingsForm.get('qrCodeConfig.badgePosition').enable();
}
} else {
if (!useDefaultApp) {
this.mobileAppQRCodeWidgetSettingsForm.get('androidConfig.appPackage').clearValidators();
this.mobileAppQRCodeWidgetSettingsForm.get('androidConfig.sha256CertFingerprints').clearValidators();
}
if (!iosEnabled) {
this.mobileAppQRCodeWidgetSettingsForm.get('qrCodeConfig.badgeEnabled').disable({emitEvent: false});
this.mobileAppQRCodeWidgetSettingsForm.get('qrCodeConfig.badgeStyle').disable();
this.mobileAppQRCodeWidgetSettingsForm.get('qrCodeConfig.badgePosition').disable();
}
}
if (iosEnabled) {
if (!useDefaultApp) {
this.mobileAppQRCodeWidgetSettingsForm.get('iosConfig.appId').setValidators([Validators.required]);
}
this.mobileAppQRCodeWidgetSettingsForm.get('qrCodeConfig.badgeEnabled').enable({emitEvent: false});
if (badgeEnabled) {
this.mobileAppQRCodeWidgetSettingsForm.get('qrCodeConfig.badgeStyle').enable();
this.mobileAppQRCodeWidgetSettingsForm.get('qrCodeConfig.badgePosition').enable();
}
} else {
if (!useDefaultApp) {
this.mobileAppQRCodeWidgetSettingsForm.get('iosConfig.appId').clearValidators();
}
if (!androidEnabled) {
this.mobileAppQRCodeWidgetSettingsForm.get('qrCodeConfig.badgeEnabled').disable({emitEvent: false});
this.mobileAppQRCodeWidgetSettingsForm.get('qrCodeConfig.badgeStyle').disable();
this.mobileAppQRCodeWidgetSettingsForm.get('qrCodeConfig.badgePosition').disable();
}
}
if (badgeEnabled) {
if (androidEnabled || iosEnabled) {
this.mobileAppQRCodeWidgetSettingsForm.get('qrCodeConfig.badgeStyle').enable();
this.mobileAppQRCodeWidgetSettingsForm.get('qrCodeConfig.badgePosition').enable();
}
} else {
this.mobileAppQRCodeWidgetSettingsForm.get('qrCodeConfig.badgeStyle').disable();
this.mobileAppQRCodeWidgetSettingsForm.get('qrCodeConfig.badgePosition').disable();
}
if (qrCodeLabelEnabled) {
this.mobileAppQRCodeWidgetSettingsForm.get('qrCodeConfig.qrCodeLabel').enable();
} else {
this.mobileAppQRCodeWidgetSettingsForm.get('qrCodeConfig.qrCodeLabel').disable();
}
this.mobileAppQRCodeWidgetSettingsForm.get('androidConfig.appPackage').updateValueAndValidity({emitEvent});
this.mobileAppQRCodeWidgetSettingsForm.get('androidConfig.sha256CertFingerprints').updateValueAndValidity({emitEvent});
this.mobileAppQRCodeWidgetSettingsForm.get('iosConfig.appId').updateValueAndValidity({emitEvent});
}
}

View File

@ -354,10 +354,14 @@ import {
import { import {
RadarChartWidgetSettingsComponent RadarChartWidgetSettingsComponent
} from '@home/components/widget/lib/settings/chart/radar-chart-widget-settings.component'; } from '@home/components/widget/lib/settings/chart/radar-chart-widget-settings.component';
import {
MobileAppQrCodeWidgetSettingsComponent
} from '@home/components/widget/lib/settings/cards/mobile-app-qr-code-widget-settings.component';
@NgModule({ @NgModule({
declarations: [ declarations: [
QrCodeWidgetSettingsComponent, QrCodeWidgetSettingsComponent,
MobileAppQrCodeWidgetSettingsComponent,
TimeseriesTableWidgetSettingsComponent, TimeseriesTableWidgetSettingsComponent,
TimeseriesTableKeySettingsComponent, TimeseriesTableKeySettingsComponent,
TimeseriesTableLatestKeySettingsComponent, TimeseriesTableLatestKeySettingsComponent,
@ -490,6 +494,7 @@ import {
], ],
exports: [ exports: [
QrCodeWidgetSettingsComponent, QrCodeWidgetSettingsComponent,
MobileAppQrCodeWidgetSettingsComponent,
TimeseriesTableWidgetSettingsComponent, TimeseriesTableWidgetSettingsComponent,
TimeseriesTableKeySettingsComponent, TimeseriesTableKeySettingsComponent,
TimeseriesTableLatestKeySettingsComponent, TimeseriesTableLatestKeySettingsComponent,
@ -620,6 +625,7 @@ export class WidgetSettingsModule {
export const widgetSettingsComponentsMap: {[key: string]: Type<IWidgetSettingsComponent>} = { export const widgetSettingsComponentsMap: {[key: string]: Type<IWidgetSettingsComponent>} = {
'tb-qrcode-widget-settings': QrCodeWidgetSettingsComponent, 'tb-qrcode-widget-settings': QrCodeWidgetSettingsComponent,
'tb-mobile-app-qr-code-widget-settings': MobileAppQrCodeWidgetSettingsComponent,
'tb-timeseries-table-widget-settings': TimeseriesTableWidgetSettingsComponent, 'tb-timeseries-table-widget-settings': TimeseriesTableWidgetSettingsComponent,
'tb-timeseries-table-key-settings': TimeseriesTableKeySettingsComponent, 'tb-timeseries-table-key-settings': TimeseriesTableKeySettingsComponent,
'tb-timeseries-table-latest-key-settings': TimeseriesTableLatestKeySettingsComponent, 'tb-timeseries-table-latest-key-settings': TimeseriesTableLatestKeySettingsComponent,

View File

@ -27,7 +27,7 @@ import {
badgePositionTranslationsMap, badgePositionTranslationsMap,
BadgeStyle, BadgeStyle,
badgeStyleTranslationsMap, badgeStyleTranslationsMap,
MobileAppSettings MobileAppQRCodeSettings
} from '@shared/models/mobile-app.models'; } from '@shared/models/mobile-app.models';
@Component({ @Component({
@ -39,7 +39,7 @@ export class MobileAppSettingsComponent extends PageComponent implements HasConf
mobileAppSettingsForm: FormGroup; mobileAppSettingsForm: FormGroup;
mobileAppSettings: MobileAppSettings; mobileAppSettings: MobileAppQRCodeSettings;
private readonly destroy$ = new Subject<void>(); private readonly destroy$ = new Subject<void>();
@ -179,7 +179,7 @@ export class MobileAppSettingsComponent extends PageComponent implements HasConf
}); });
} }
private processMobileAppSettings(mobileAppSettings: MobileAppSettings): void { private processMobileAppSettings(mobileAppSettings: MobileAppQRCodeSettings): void {
this.mobileAppSettings = {...mobileAppSettings}; this.mobileAppSettings = {...mobileAppSettings};
this.mobileAppSettingsForm.reset(this.mobileAppSettings); this.mobileAppSettingsForm.reset(this.mobileAppSettings);
} }

View File

@ -16,7 +16,7 @@
import { TenantId } from '@shared/models/id/tenant-id'; import { TenantId } from '@shared/models/id/tenant-id';
export interface MobileAppSettings { export interface MobileAppQRCodeSettings {
tenantId: TenantId; tenantId: TenantId;
useDefaultApp: boolean; useDefaultApp: boolean;
androidConfig: AndroidConfig; androidConfig: AndroidConfig;

View File

@ -1145,7 +1145,7 @@
"id": "8e71a398-caf5-540d-cec5-6e5dc264343e" "id": "8e71a398-caf5-540d-cec5-6e5dc264343e"
}, },
"b9d704a2-f579-dbff-f123-f953d92081fd": { "b9d704a2-f579-dbff-f123-f953d92081fd": {
"typeFullFqn": "system.home_page_widgets.mobile_app_qr_code", "typeFullFqn": "system.home_page_widgets.home_mobile_app_qr_code",
"type": "static", "type": "static",
"sizeX": 6, "sizeX": 6,
"sizeY": 3, "sizeY": 3,
@ -1176,12 +1176,10 @@
"backgroundColor": "rgb(255, 255, 255)", "backgroundColor": "rgb(255, 255, 255)",
"color": "rgba(0, 0, 0, 0.87)", "color": "rgba(0, 0, 0, 0.87)",
"padding": "8px", "padding": "8px",
"settings": { "settings": {},
"cardHtml": "<div class='card'>HTML code here</div>", "title": "Mobile app QR code",
"cardCss": ".card {\n font-weight: bold;\n font-size: 32px;\n color: #999;\n width: 100%;\n height: 100%;\n display: flex;\n align-items: center;\n justify-content: center;\n}" "dropShadow": false,
}, "enableFullscreen": false
"title": "HTML Card",
"dropShadow": false
}, },
"row": 0, "row": 0,
"col": 0, "col": 0,

View File

@ -290,9 +290,6 @@
&.shrink { &.shrink {
flex: 0; flex: 0;
} }
&.no-gap {
gap: 0;
}
} }
.tb-form-panel, .tb-form-row { .tb-form-panel, .tb-form-row {