Browse Source

UI: mobile app QR code UI initial implementation

pull/10591/head
rusikv 2 years ago
parent
commit
a057f42e95
  1. 3
      application/src/main/data/json/system/widget_bundles/home_page_widgets.json
  2. 21
      application/src/main/data/json/system/widget_types/mobile_app_qr_code.json
  3. 45
      ui-ngx/src/app/core/http/mobile-app.service.ts
  4. 8
      ui-ngx/src/app/core/services/menu.service.ts
  5. 5
      ui-ngx/src/app/core/services/utils.service.ts
  6. 40
      ui-ngx/src/app/modules/home/components/widget/lib/mobile-app-qrcode-widget.component.html
  7. 56
      ui-ngx/src/app/modules/home/components/widget/lib/mobile-app-qrcode-widget.component.scss
  8. 111
      ui-ngx/src/app/modules/home/components/widget/lib/mobile-app-qrcode-widget.component.ts
  9. 3
      ui-ngx/src/app/modules/home/components/widget/widget-components.module.ts
  10. 14
      ui-ngx/src/app/modules/home/pages/admin/admin-routing.module.ts
  11. 18
      ui-ngx/src/app/modules/home/pages/admin/admin.module.ts
  12. 161
      ui-ngx/src/app/modules/home/pages/admin/mobile-app-settings.component.html
  13. 28
      ui-ngx/src/app/modules/home/pages/admin/mobile-app-settings.component.scss
  14. 197
      ui-ngx/src/app/modules/home/pages/admin/mobile-app-settings.component.ts
  15. 81
      ui-ngx/src/app/shared/models/mobile-app.models.ts
  16. 22
      ui-ngx/src/assets/android-ios-stores-badges/android_store_en_black_badge.svg
  17. 46
      ui-ngx/src/assets/android-ios-stores-badges/ios_store_en_black_badge.svg
  18. 46
      ui-ngx/src/assets/android-ios-stores-badges/ios_store_en_white_badge.svg
  19. 53
      ui-ngx/src/assets/dashboard/tenant_admin_home_page.json
  20. 27
      ui-ngx/src/assets/locale/locale.constant-en_US.json
  21. 15
      ui-ngx/src/form.scss

3
application/src/main/data/json/system/widget_bundles/home_page_widgets.json

@ -13,6 +13,7 @@
"home_page_widgets.quick_links",
"home_page_widgets.documentation_links",
"home_page_widgets.dashboards",
"home_page_widgets.usage_info"
"home_page_widgets.usage_info",
"home_page_widgets.mobile_app_qr_code"
]
}

21
application/src/main/data/json/system/widget_types/mobile_app_qr_code.json

@ -0,0 +1,21 @@
{
"fqn": "home_page_widgets.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 [ctx]=\"ctx\">\n</tb-mobile-app-qrcode-widget>",
"templateCss": "",
"controllerScript": "self.onInit = function() {\n self.ctx.$scope.mobileAppQrcodeWidget.ngOnInit();\n}\n\nself.onDataUpdated = function() {\n self.ctx.$scope.mobileAppQrcodeWidget.ngOnDestroy();\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\":\"HTML Card\",\"dropShadow\":true}"
},
"tags": null
}

45
ui-ngx/src/app/core/http/mobile-app.service.ts

@ -0,0 +1,45 @@
///
/// 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 { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { defaultHttpOptionsFromConfig, RequestConfig } from '@core/http/http-utils';
import { Observable } from 'rxjs';
import { MobileAppSettings } from '@shared/models/mobile-app.models';
@Injectable({
providedIn: 'root'
})
export class MobileAppService {
constructor(
private http: HttpClient
) {
}
public getMobileAppSettings(config?: RequestConfig): Observable<MobileAppSettings> {
return this.http.get<MobileAppSettings>(`/api/mobile/app/settings`, defaultHttpOptionsFromConfig(config));
}
public saveMobileAppSettings(mobileAppSettings: MobileAppSettings, config?: RequestConfig): Observable<MobileAppSettings> {
return this.http.post<MobileAppSettings>(`/api/mobile/app/settings`, mobileAppSettings, defaultHttpOptionsFromConfig(config));
}
public getMobileAppDeepLink( config?: RequestConfig): Observable<string> {
return this.http.get<string>(`/api/mobile/deepLink`, defaultHttpOptionsFromConfig(config));
}
}

8
ui-ngx/src/app/core/services/menu.service.ts

@ -245,6 +245,14 @@ export class MenuService {
path: '/settings/queues',
icon: 'swap_calls'
},
{
id: 'mobile_app_settings',
name: 'admin.mobile-app.mobile-app',
fullName: 'admin.mobile-app.mobile-app',
type: 'link',
path: '/settings/mobile-app',
icon: 'smartphone'
}
]
},
{

5
ui-ngx/src/app/core/services/utils.service.ts

@ -30,7 +30,7 @@ import {
guid,
hashCode,
isDefined,
isDefinedAndNotNull,
isDefinedAndNotNull, isNotEmptyStr,
isString,
isUndefined,
objToBase64,
@ -386,8 +386,7 @@ export class UtilsService {
this.window.performance.now() : Date.now();
}
public getQueryParam(name: string): string {
const url = this.window.location.href;
public getQueryParam(name: string, url = this.window.location.href): string {
name = name.replace(/[\[\]]/g, '\\$&');
const regex = new RegExp('[?&]' + name + '(=([^&#]*)|&|#|$)');
const results = regex.exec(url);

40
ui-ngx/src/app/modules/home/components/widget/lib/mobile-app-qrcode-widget.component.html

@ -0,0 +1,40 @@
<!--
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 *ngIf="!previewMode" class="tb-title" translate>admin.mobile-app.connect-mobile-app</div>
<div class="tb-flex column center align-center">
<div class="tb-flex row align-center shrink" style="margin-bottom: 8px"
[class.row]="mobileAppSettings?.qrCodeConfig.badgePosition === badgePosition.RIGHT"
[class.row-reverse]="mobileAppSettings?.qrCodeConfig.badgePosition === badgePosition.LEFT">
<canvas #canvas class="tb-qrcode"></canvas>
<div *ngIf="mobileAppSettings?.qrCodeConfig.badgeEnabled"
class="tb-flex column no-gap tb-badge-container"
[class.tb-badge-container]="!previewMode">
<ng-container *ngIf="mobileAppSettings.qrCodeConfig.badgeStyle === badgeStyle.ORIGINAL;else white">
<img *ngIf="mobileAppSettings.iosConfig.enabled" [src]="badgeStyleURLMap.get(badgeStyle.ORIGINAL).iOS">
<img *ngIf="mobileAppSettings.androidConfig.enabled" [src]="badgeStyleURLMap.get(badgeStyle.ORIGINAL).android">
</ng-container>
<ng-template #white>
<img *ngIf="mobileAppSettings.iosConfig.enabled" [src]="badgeStyleURLMap.get(badgeStyle.WHITE).iOS">
<img *ngIf="mobileAppSettings.androidConfig.enabled" [src]="badgeStyleURLMap.get(badgeStyle.WHITE).android">
</ng-template>
</div>
</div>
<div *ngIf="mobileAppSettings?.qrCodeConfig.qrCodeLabelEnabled" class="tb-qrcode-label">
{{ mobileAppSettings.qrCodeConfig.qrCodeLabel }}
</div>
</div>

56
ui-ngx/src/app/modules/home/components/widget/lib/mobile-app-qrcode-widget.component.scss

@ -0,0 +1,56 @@
/**
* 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';
:host {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
background-color: transparent;
}
.tb-title {
align-self: start;
font-weight: 600;
font-size: 20px;
line-height: 24px;
letter-spacing: 0.1px;
color: rgba(0, 0, 0, 0.76);
@media #{$mat-md-lg} {
font-weight: 500;
font-size: 14px;
line-height: 20px;
letter-spacing: 0.25px;
}
}
.tb-qrcode-label {
font-size: 14px;
color: rgba(0, 0, 0, 0.54);
}
.tb-qrcode {
box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.08);
border-radius: 6px;
}
.tb-badge-container {
@media #{$mat-lt-lg} {
display: none;
}
}

111
ui-ngx/src/app/modules/home/components/widget/lib/mobile-app-qrcode-widget.component.ts

@ -0,0 +1,111 @@
///
/// 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 { AfterViewInit, ChangeDetectorRef, Component, ElementRef, Input, OnDestroy, OnInit, ViewChild } from '@angular/core';
import { PageComponent } from '@shared/components/page.component';
import { AppState } from '@core/core.state';
import { Store } from '@ngrx/store';
import { BadgePosition, BadgeStyle, badgeStyleURLMap, MobileAppSettings } from '@shared/models/mobile-app.models';
import { MobileAppService } from '@core/http/mobile-app.service';
import { WidgetContext } from '@home/models/widget-component.models';
import { UtilsService } from '@core/services/utils.service';
import { interval, mergeMap, Observable, Subject, takeUntil } from 'rxjs';
import { MINUTE } from '@shared/models/time/time.models';
import { coerceBoolean } from '@shared/decorators/coercion';
@Component({
selector: 'tb-mobile-app-qrcode-widget',
templateUrl: './mobile-app-qrcode-widget.component.html',
styleUrls: ['./mobile-app-qrcode-widget.component.scss']
})
export class MobileAppQrcodeWidgetComponent extends PageComponent implements OnInit, AfterViewInit, OnDestroy {
@Input()
ctx: WidgetContext;
@Input()
@coerceBoolean()
previewMode: boolean;
@Input()
set mobileAppSettings(settings: MobileAppSettings) {
if (settings) {
this.mobileAppSettingsValue = settings;
}
};
get mobileAppSettings() {
return this.mobileAppSettingsValue;
}
@ViewChild('canvas', {static: false}) canvasRef: ElementRef<HTMLCanvasElement>;
private readonly destroy$ = new Subject<void>();
badgeStyle = BadgeStyle;
badgePosition = BadgePosition;
badgeStyleURLMap = badgeStyleURLMap;
private mobileAppSettingsValue: MobileAppSettings;
private deepLinkTTL: number;
constructor(protected store: Store<AppState>,
protected cd: ChangeDetectorRef,
private mobileAppService: MobileAppService,
private utilsService: UtilsService) {
super(store);
}
ngOnInit(): void {
if (this.ctx) {
this.ctx.$scope.mobileAppQrcodeWidget = this;
}
if (!this.mobileAppSettings) {
this.mobileAppService.getMobileAppSettings().subscribe((settings => {
this.mobileAppSettings = settings;
this.cd.detectChanges();
}));
}
}
ngAfterViewInit(): void {
this.getMobileAppDeepLink().subscribe(link => {
this.deepLinkTTL = Number(this.utilsService.getQueryParam('ttl', link)) * MINUTE;
this.updateQRCode(link);
interval(this.deepLinkTTL).pipe(
takeUntil(this.destroy$),
mergeMap(() => this.getMobileAppDeepLink())
).subscribe(link => this.updateQRCode(link));
});
}
ngOnDestroy() {
super.ngOnDestroy();
this.destroy$.next();
this.destroy$.complete();
}
getMobileAppDeepLink(): Observable<string> {
return this.mobileAppService.getMobileAppDeepLink();
}
updateQRCode(link: string) {
import('qrcode').then((QRCode) => {
QRCode.toCanvas(this.canvasRef.nativeElement, link, { width: 125});
});
}
}

3
ui-ngx/src/app/modules/home/components/widget/widget-components.module.ts

@ -92,6 +92,7 @@ import { PieChartWidgetComponent } from '@home/components/widget/lib/chart/pie-c
import { BarChartWidgetComponent } from '@home/components/widget/lib/chart/bar-chart-widget.component';
import { PolarAreaWidgetComponent } from '@home/components/widget/lib/chart/polar-area-widget.component';
import { RadarChartWidgetComponent } from '@home/components/widget/lib/chart/radar-chart-widget.component';
import { MobileAppQrcodeWidgetComponent } from '@home/components/widget/lib/mobile-app-qrcode-widget.component';
@NgModule({
declarations:
@ -112,6 +113,7 @@ import { RadarChartWidgetComponent } from '@home/components/widget/lib/chart/rad
NavigationCardsWidgetComponent,
NavigationCardWidgetComponent,
QrCodeWidgetComponent,
MobileAppQrcodeWidgetComponent,
MarkdownWidgetComponent,
SelectEntityDialogComponent,
LegendComponent,
@ -176,6 +178,7 @@ import { RadarChartWidgetComponent } from '@home/components/widget/lib/chart/rad
NavigationCardsWidgetComponent,
NavigationCardWidgetComponent,
QrCodeWidgetComponent,
MobileAppQrcodeWidgetComponent,
MarkdownWidgetComponent,
LegendComponent,
FlotWidgetComponent,

14
ui-ngx/src/app/modules/home/pages/admin/admin-routing.module.ts

@ -40,6 +40,7 @@ import { widgetsLibraryRoutes } from '@home/pages/widget/widget-library-routing.
import { RouterTabsComponent } from '@home/components/router-tabs.component';
import { auditLogsRoutes } from '@home/pages/audit-log/audit-log-routing.module';
import { ImageGalleryComponent } from '@shared/components/image/image-gallery.component';
import { MobileAppSettingsComponent } from '@home/pages/admin/mobile-app-settings.component';
@Injectable()
export class OAuth2LoginProcessingUrlResolver implements Resolve<string> {
@ -270,6 +271,19 @@ const routes: Routes = [
}
}
},
{
path: 'mobile-app',
component: MobileAppSettingsComponent,
canDeactivate: [ConfirmOnExitGuard],
data: {
auth: [Authority.SYS_ADMIN],
title: 'admin.mobile-app.mobile-app',
breadcrumb: {
label: 'admin.mobile-app.mobile-app',
icon: 'smartphone'
}
}
},
{
path: 'security-settings',
redirectTo: '/security-settings/general'

18
ui-ngx/src/app/modules/home/pages/admin/admin.module.ts

@ -33,6 +33,8 @@ import { QueueComponent } from '@home/pages/admin/queue/queue.component';
import { RepositoryAdminSettingsComponent } from '@home/pages/admin/repository-admin-settings.component';
import { AutoCommitAdminSettingsComponent } from '@home/pages/admin/auto-commit-admin-settings.component';
import { TwoFactorAuthSettingsComponent } from '@home/pages/admin/two-factor-auth-settings.component';
import { MobileAppSettingsComponent } from '@home/pages/admin/mobile-app-settings.component';
import { WidgetComponentsModule } from '@home/components/widget/widget-components.module';
@NgModule({
declarations:
@ -49,13 +51,15 @@ import { TwoFactorAuthSettingsComponent } from '@home/pages/admin/two-factor-aut
QueueComponent,
RepositoryAdminSettingsComponent,
AutoCommitAdminSettingsComponent,
TwoFactorAuthSettingsComponent
TwoFactorAuthSettingsComponent,
MobileAppSettingsComponent
],
imports: [
CommonModule,
SharedModule,
HomeComponentsModule,
AdminRoutingModule
]
imports: [
CommonModule,
SharedModule,
HomeComponentsModule,
AdminRoutingModule,
WidgetComponentsModule
]
})
export class AdminModule { }

161
ui-ngx/src/app/modules/home/pages/admin/mobile-app-settings.component.html

@ -0,0 +1,161 @@
<!--
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-card appearance="outlined" class="settings-card">
<mat-card-header>
<mat-card-title>
<span class="mat-headline-5" translate>admin.mobile-app.mobile-app-qr-code-widget-settings</span>
</mat-card-title>
</mat-card-header>
<mat-progress-bar color="warn" mode="indeterminate" *ngIf="isLoading$ | async">
</mat-progress-bar>
<div style="height: 4px;" *ngIf="!(isLoading$ | async)"></div>
<mat-card-content style="padding-top: 16px">
<div class="tb-form-panel no-border no-padding" [formGroup]="mobileAppSettingsForm">
<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="!mobileAppSettingsForm.get('useDefaultApp').value && mobileAppSettingsForm.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="mobileAppSettingsForm.get('androidConfig.appPackage').hasError('required')
&& mobileAppSettingsForm.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="!mobileAppSettingsForm.get('useDefaultApp').value && mobileAppSettingsForm.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="mobileAppSettingsForm.get('androidConfig.sha256CertFingerprints').hasError('required')
&& mobileAppSettingsForm.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="!mobileAppSettingsForm.get('useDefaultApp').value && mobileAppSettingsForm.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="mobileAppSettingsForm.get('iosConfig.appId').hasError('required')
&& mobileAppSettingsForm.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-flex row space-between align-center">
<div class="tb-form-panel-title" translate>admin.mobile-app.appearance-on-home-page</div>
<tb-toggle-select formControlName="showOnHomePage">
<tb-toggle-option [value]="true">{{ 'admin.mobile-app.enabled' | translate }}</tb-toggle-option>
<tb-toggle-option [value]="false">{{ 'admin.mobile-app.disabled' | translate }}</tb-toggle-option>
</tb-toggle-select>
</div>
<div class="tb-form-panel stroked no-padding no-gap" *ngIf="mobileAppSettingsForm.get('qrCodeConfig.showOnHomePage').value">
<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" *ngIf="mobileAppSettingsForm.get('qrCodeConfig.showOnHomePage').value">
<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 class="tb-form-panel tb-qrcode-preview" *ngIf="mobileAppSettingsForm.get('qrCodeConfig.showOnHomePage').value">
<div class="tb-form-panel-title" translate>admin.mobile-app.preview</div>
<tb-mobile-app-qrcode-widget previewMode
[mobileAppSettings]="mobileAppSettingsForm.getRawValue()">
</tb-mobile-app-qrcode-widget>
</div>
</div>
</div>
<div class="tb-flex row flex-end" style="margin-top: 16px">
<button mat-button mat-raised-button color="primary" (click)="save()"
[disabled]="(isLoading$ | async) || mobileAppSettingsForm.invalid || !mobileAppSettingsForm.dirty">
{{ 'action.save' | translate }}
</button>
</div>
</mat-card-content>
</mat-card>

28
ui-ngx/src/app/modules/home/pages/admin/mobile-app-settings.component.scss

@ -0,0 +1,28 @@
/**
* 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;
}
.tb-qrcode-preview {
background-color: rgba(0, 0, 0, 0.04);
box-shadow: none;
.tb-form-panel-title {
color: rgba(0, 0, 0, 0.38);
}
}

197
ui-ngx/src/app/modules/home/pages/admin/mobile-app-settings.component.ts

@ -0,0 +1,197 @@
///
/// 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, OnDestroy } from '@angular/core';
import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state';
import { PageComponent } from '@shared/components/page.component';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { HasConfirmForm } from '@core/guards/confirm-on-exit.guard';
import { Subject, takeUntil } from 'rxjs';
import { MobileAppService } from '@core/http/mobile-app.service';
import {
BadgePosition,
badgePositionTranslationsMap,
BadgeStyle,
badgeStyleTranslationsMap,
MobileAppSettings
} from '@shared/models/mobile-app.models';
@Component({
selector: 'tb-mobile-app-settings',
templateUrl: './mobile-app-settings.component.html',
styleUrls: ['mobile-app-settings.component.scss', './settings-card.scss']
})
export class MobileAppSettingsComponent extends PageComponent implements HasConfirmForm, OnDestroy {
mobileAppSettingsForm: FormGroup;
mobileAppSettings: MobileAppSettings;
private readonly destroy$ = new Subject<void>();
badgePositionTranslationsMap = badgePositionTranslationsMap;
badgeStyleTranslationsMap = badgeStyleTranslationsMap;
constructor(protected store: Store<AppState>,
private mobileAppService: MobileAppService,
public fb: FormBuilder) {
super(store);
this.buildMobileAppSettingsForm();
this.mobileAppService.getMobileAppSettings()
.subscribe(settings => this.processMobileAppSettings(settings));
this.mobileAppSettingsForm.get('useDefaultApp').valueChanges.pipe(
takeUntil(this.destroy$)
).subscribe(value => {
if (value) {
this.mobileAppSettingsForm.get('androidConfig.appPackage').clearValidators();
this.mobileAppSettingsForm.get('androidConfig.sha256CertFingerprints').clearValidators();
this.mobileAppSettingsForm.get('iosConfig.appId').clearValidators();
} else {
this.mobileAppSettingsForm.get('androidConfig.appPackage').setValidators([Validators.required]);
this.mobileAppSettingsForm.get('androidConfig.sha256CertFingerprints').setValidators([Validators.required]);
this.mobileAppSettingsForm.get('iosConfig.appId').setValidators([Validators.required]);
}
this.mobileAppSettingsForm.get('androidConfig.appPackage').updateValueAndValidity();
this.mobileAppSettingsForm.get('androidConfig.sha256CertFingerprints').updateValueAndValidity();
this.mobileAppSettingsForm.get('iosConfig.appId').updateValueAndValidity();
});
this.mobileAppSettingsForm.get('androidConfig.enabled').valueChanges.pipe(
takeUntil(this.destroy$)
).subscribe(value => {
if (!this.mobileAppSettingsForm.get('useDefaultApp').value) {
if (value) {
this.mobileAppSettingsForm.get('androidConfig.appPackage').setValidators([Validators.required]);
this.mobileAppSettingsForm.get('androidConfig.sha256CertFingerprints').setValidators([Validators.required]);
} else {
this.mobileAppSettingsForm.get('androidConfig.appPackage').clearValidators();
this.mobileAppSettingsForm.get('androidConfig.sha256CertFingerprints').clearValidators();
}
this.mobileAppSettingsForm.get('androidConfig.appPackage').updateValueAndValidity();
this.mobileAppSettingsForm.get('androidConfig.sha256CertFingerprints').updateValueAndValidity();
}
if (this.mobileAppSettingsForm.get('qrCodeConfig.showOnHomePage').value) {
if (value) {
this.mobileAppSettingsForm.get('qrCodeConfig.badgeEnabled').enable();
if (this.mobileAppSettingsForm.get('qrCodeConfig.badgeEnabled').value) {
this.mobileAppSettingsForm.get('qrCodeConfig.badgeStyle').enable();
this.mobileAppSettingsForm.get('qrCodeConfig.badgePosition').enable();
}
} else {
if (!this.mobileAppSettingsForm.get('iosConfig.enabled').value) {
this.mobileAppSettingsForm.get('qrCodeConfig.badgeEnabled').disable();
this.mobileAppSettingsForm.get('qrCodeConfig.badgeStyle').disable();
this.mobileAppSettingsForm.get('qrCodeConfig.badgePosition').disable();
}
}
}
});
this.mobileAppSettingsForm.get('iosConfig.enabled').valueChanges.pipe(
takeUntil(this.destroy$)
).subscribe(value => {
if (!this.mobileAppSettingsForm.get('useDefaultApp').value) {
if (value) {
this.mobileAppSettingsForm.get('iosConfig.appId').setValidators([Validators.required]);
} else {
this.mobileAppSettingsForm.get('iosConfig.appId').clearValidators();
}
this.mobileAppSettingsForm.get('iosConfig.appId').updateValueAndValidity();
}
if (this.mobileAppSettingsForm.get('qrCodeConfig.showOnHomePage').value) {
if (value) {
this.mobileAppSettingsForm.get('qrCodeConfig.badgeEnabled').enable();
if (this.mobileAppSettingsForm.get('qrCodeConfig.badgeEnabled').value) {
this.mobileAppSettingsForm.get('qrCodeConfig.badgeStyle').enable();
this.mobileAppSettingsForm.get('qrCodeConfig.badgePosition').enable();
}
} else {
if (!this.mobileAppSettingsForm.get('androidConfig.enabled').value) {
this.mobileAppSettingsForm.get('qrCodeConfig.badgeEnabled').disable();
this.mobileAppSettingsForm.get('qrCodeConfig.badgeStyle').disable();
this.mobileAppSettingsForm.get('qrCodeConfig.badgePosition').disable();
}
}
}
});
this.mobileAppSettingsForm.get('qrCodeConfig.badgeEnabled').valueChanges.pipe(
takeUntil(this.destroy$)
).subscribe(value => {
if (value) {
if (this.mobileAppSettingsForm.get('androidConfig.enabled').value || this.mobileAppSettingsForm.get('iosConfig.enabled').value) {
this.mobileAppSettingsForm.get('qrCodeConfig.badgeStyle').enable();
this.mobileAppSettingsForm.get('qrCodeConfig.badgePosition').enable();
}
} else {
this.mobileAppSettingsForm.get('qrCodeConfig.badgeStyle').disable();
this.mobileAppSettingsForm.get('qrCodeConfig.badgePosition').disable();
}
});
this.mobileAppSettingsForm.get('qrCodeConfig.qrCodeLabelEnabled').valueChanges.pipe(
takeUntil(this.destroy$)
).subscribe(value => {
if (value) {
this.mobileAppSettingsForm.get('qrCodeConfig.qrCodeLabel').enable();
} else {
this.mobileAppSettingsForm.get('qrCodeConfig.qrCodeLabel').disable();
}
});
}
ngOnDestroy() {
super.ngOnDestroy();
this.destroy$.next();
this.destroy$.complete();
}
private buildMobileAppSettingsForm() {
this.mobileAppSettingsForm = this.fb.group({
useDefaultApp: [true, []],
androidConfig: this.fb.group({
enabled: [true, []],
appPackage: ['', []],
sha256CertFingerprints: ['', []]
}),
iosConfig: this.fb.group({
enabled: [true, []],
appId: ['', []]
}),
qrCodeConfig: this.fb.group({
showOnHomePage: [true, []],
badgeEnabled: [true, []],
badgeStyle: [{value: BadgeStyle.ORIGINAL, disabled: true}, []],
badgePosition: [{value: BadgePosition.RIGHT, disabled: true}, []],
qrCodeLabelEnabled: [true, []],
qrCodeLabel: ['', []]
})
});
}
private processMobileAppSettings(mobileAppSettings: MobileAppSettings): void {
this.mobileAppSettings = {...mobileAppSettings};
this.mobileAppSettingsForm.reset(this.mobileAppSettings);
}
save(): void {
this.mobileAppSettings = {...this.mobileAppSettings, ...this.mobileAppSettingsForm.getRawValue()};
this.mobileAppService.saveMobileAppSettings(this.mobileAppSettings)
.subscribe((settings) => this.processMobileAppSettings(settings));
}
confirmForm(): FormGroup {
return this.mobileAppSettingsForm;
}
}

81
ui-ngx/src/app/shared/models/mobile-app.models.ts

@ -0,0 +1,81 @@
///
/// 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 { TenantId } from '@shared/models/id/tenant-id';
export interface MobileAppSettings {
tenantId: TenantId;
useDefaultApp: boolean;
androidConfig: AndroidConfig;
iosConfig: IosConfig;
qrCodeConfig: QRCodeConfig;
}
export interface AndroidConfig {
enabled: boolean;
appPackage: string;
sha256CertFingerprints: string
}
export interface IosConfig {
enabled: boolean;
appId: string;
}
export interface QRCodeConfig {
showOnHomePage: boolean;
badgeEnabled: boolean;
badgePosition: BadgePosition;
badgeStyle: BadgeStyle;
qrCodeLabelEnabled: boolean;
qrCodeLabel: string;
}
export interface MobileOSBadgeURL {
iOS: string;
android: string;
}
export enum BadgePosition {
RIGHT = 'RIGHT',
LEFT = 'LEFT'
}
export const badgePositionTranslationsMap = new Map<BadgePosition, string>([
[BadgePosition.RIGHT, 'admin.mobile-app.right'],
[BadgePosition.LEFT, 'admin.mobile-app.left']
]);
export enum BadgeStyle {
ORIGINAL = 'ORIGINAL',
WHITE = 'WHITE'
}
export const badgeStyleTranslationsMap = new Map<BadgeStyle, string>([
[BadgeStyle.ORIGINAL, 'admin.mobile-app.original'],
[BadgeStyle.WHITE, 'admin.mobile-app.white']
]);
export const badgeStyleURLMap = new Map<BadgeStyle, MobileOSBadgeURL>([
[BadgeStyle.ORIGINAL, {
iOS: 'assets/android-ios-stores-badges/ios_store_en_black_badge.svg',
android: 'assets/android-ios-stores-badges/android_store_en_black_badge.svg'
}],
[BadgeStyle.WHITE, {
iOS: 'assets/android-ios-stores-badges/ios_store_en_white_badge.svg',
android: 'assets/android-ios-stores-badges/android_store_en_black_badge.svg'
}]
]);

22
ui-ngx/src/assets/android-ios-stores-badges/android_store_en_black_badge.svg

@ -0,0 +1,22 @@
<svg width="119.66407" height="40" viewBox="0 0 135 40" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_591_70869)">
<mask id="mask0_591_70869" style="mask-type:luminance" maskUnits="userSpaceOnUse" x="0" y="0" width="135" height="40">
<path d="M0 0H135V40H0V0Z" fill="white"/>
</mask>
<g mask="url(#mask0_591_70869)">
<path d="M130 40H5.00006C2.25003 40 0 37.7501 0 35V4.99998C0 2.24982 2.25003 -4.57764e-05 5.00006 -4.57764e-05H130C132.75 -4.57764e-05 135 2.24982 135 4.99998V35C135 37.7501 132.75 40 130 40Z" fill="black"/>
<path d="M130 0H5.00006C2.25003 0 0 2.25008 0 5.00003V35C0 37.7501 2.25003 40 5.00006 40H130C132.75 40 135 37.7501 135 35V5.00003C135 2.25008 132.75 0 130 0ZM130 0.799973C132.316 0.799973 134.2 2.68391 134.2 5.00003V35C134.2 37.3161 132.316 39.2001 130 39.2001H5.00006C2.68399 39.2001 0.799996 37.3161 0.799996 35V5.00003C0.799996 2.68391 2.68399 0.799973 5.00006 0.799973H130Z" fill="#A2A2A1"/>
<path d="M106.936 30H108.802V17.4991H106.936V30ZM123.743 22.002L121.604 27.4221H121.54L119.32 22.002H117.31L120.639 29.5771L118.741 33.7911H120.687L125.818 22.002H123.743ZM113.161 28.58C112.549 28.58 111.697 28.2741 111.697 27.5182C111.697 26.5531 112.758 26.184 113.675 26.184C114.495 26.184 114.881 26.3611 115.38 26.602C115.235 27.7602 114.238 28.58 113.161 28.58ZM113.386 21.7291C112.035 21.7291 110.635 22.324 110.056 23.6421L111.713 24.3342C112.067 23.6421 112.726 23.417 113.418 23.417C114.383 23.417 115.364 23.9961 115.38 25.0251V25.154C115.042 24.9611 114.318 24.6721 113.434 24.6721C111.649 24.6721 109.831 25.6531 109.831 27.486C109.831 29.1591 111.295 30.2362 112.935 30.2362C114.19 30.2362 114.881 29.6741 115.316 29.0141H115.38V29.9791H117.181V25.1862C117.181 22.9671 115.525 21.7291 113.386 21.7291ZM101.854 23.5242H99.1998V19.238H101.854C103.249 19.238 104.041 20.3941 104.041 21.3811C104.041 22.3502 103.249 23.5242 101.854 23.5242ZM101.806 17.4991H97.3349V30H99.1998V25.2641H101.806C103.874 25.2641 105.907 23.766 105.907 21.3811C105.907 18.996 103.874 17.4991 101.806 17.4991ZM77.4249 28.5822C76.1359 28.5822 75.057 27.5031 75.057 26.0211C75.057 24.5231 76.1359 23.427 77.4249 23.427C78.6978 23.427 79.6959 24.5231 79.6959 26.0211C79.6959 27.5031 78.6978 28.5822 77.4249 28.5822ZM79.5669 22.7021H79.503C79.084 22.203 78.279 21.751 77.264 21.751C75.1368 21.751 73.1879 23.6201 73.1879 26.0211C73.1879 28.4051 75.1368 30.2581 77.264 30.2581C78.279 30.2581 79.084 29.8071 79.503 29.2912H79.5669V29.9031C79.5669 31.531 78.6978 32.4002 77.296 32.4002C76.1518 32.4002 75.443 31.579 75.1529 30.8861L73.526 31.5632C73.9929 32.691 75.234 34.0762 77.296 34.0762C79.4869 34.0762 81.3399 32.7872 81.3399 29.646V22.0091H79.5669V22.7021ZM82.6278 30H84.497V17.4981H82.6278V30ZM87.2519 25.876C87.2038 24.232 88.525 23.3951 89.475 23.3951C90.2169 23.3951 90.8449 23.7652 91.0539 24.2972L87.2519 25.876ZM93.0519 24.4581C92.6968 23.5071 91.6179 21.751 89.4109 21.751C87.2199 21.751 85.3999 23.4751 85.3999 26.0052C85.3999 28.389 87.2038 30.2581 89.6198 30.2581C91.5698 30.2581 92.6968 29.0661 93.165 28.3732L91.7149 27.4062C91.2309 28.115 90.5709 28.5822 89.6198 28.5822C88.67 28.5822 87.9929 28.1471 87.5579 27.2942L93.245 24.9411L93.0519 24.4581ZM47.7439 23.0571V24.861H52.061C51.9319 25.876 51.5939 26.6171 51.079 27.132C50.45 27.761 49.467 28.4532 47.7439 28.4532C45.0849 28.4532 43.007 26.3111 43.007 23.6521C43.007 20.9941 45.0849 18.8521 47.7439 18.8521C49.1779 18.8521 50.2249 19.415 50.9979 20.14L52.2708 18.8681C51.191 17.837 49.7579 17.0471 47.7439 17.0471C44.1029 17.0471 41.042 20.0121 41.042 23.6521C41.042 27.2942 44.1029 30.2581 47.7439 30.2581C49.709 30.2581 51.191 29.613 52.3509 28.4051C53.5429 27.2131 53.914 25.5382 53.914 24.1842C53.914 23.7652 53.8818 23.3792 53.8168 23.0571H47.7439ZM58.8219 28.5822C57.5339 28.5822 56.422 27.5191 56.422 26.0052C56.422 24.474 57.5339 23.427 58.8219 23.427C60.1108 23.427 61.2228 24.474 61.2228 26.0052C61.2228 27.5191 60.1108 28.5822 58.8219 28.5822ZM58.8219 21.751C56.4698 21.751 54.5529 23.5401 54.5529 26.0052C54.5529 28.4532 56.4698 30.2581 58.8219 30.2581C61.1739 30.2581 63.0919 28.4532 63.0919 26.0052C63.0919 23.5401 61.1739 21.751 58.8219 21.751ZM68.1358 28.5822C66.8468 28.5822 65.7349 27.5191 65.7349 26.0052C65.7349 24.474 66.8468 23.427 68.1358 23.427C69.425 23.427 70.5359 24.474 70.5359 26.0052C70.5359 27.5191 69.425 28.5822 68.1358 28.5822ZM68.1358 21.751C65.784 21.751 63.8668 23.5401 63.8668 26.0052C63.8668 28.4532 65.784 30.2581 68.1358 30.2581C70.4879 30.2581 72.4048 28.4532 72.4048 26.0052C72.4048 23.5401 70.4879 21.751 68.1358 21.751Z" fill="white"/>
<path d="M20.7173 19.4242L10.0703 30.7244C10.0713 30.7262 10.0713 30.7293 10.0724 30.7312C10.3993 31.9583 11.5193 32.8613 12.8493 32.8613C13.3813 32.8613 13.8803 32.7173 14.3083 32.4653L14.3423 32.4453L26.3263 25.5303L20.7173 19.4242Z" fill="#EB3131"/>
<path d="M31.4882 17.5003L31.4782 17.4932L26.3041 14.4942L20.4751 19.6812L26.325 25.5293L31.4711 22.5603C32.373 22.0722 32.9861 21.1213 32.9861 20.0252C32.9861 18.9352 32.382 17.9893 31.4882 17.5003Z" fill="#F6B60B"/>
<path d="M10.0701 9.27748C10.0061 9.51342 9.97217 9.76148 9.97217 10.0175V29.9856C9.97217 30.2416 10.0061 30.4895 10.0712 30.7246L21.0831 19.7135L10.0701 9.27748Z" fill="#5778C5"/>
<path d="M20.7958 20.002L26.3058 14.4929L14.3358 7.55287C13.9008 7.29185 13.3928 7.14202 12.8498 7.14202C11.5199 7.14202 10.3978 8.0469 10.0708 9.27486C10.0708 9.2759 10.0698 9.27695 10.0698 9.27799L20.7958 20.002Z" fill="#3BAD49"/>
</g>
<path d="M46.5327 9.5494C46.5327 10.2924 46.347 10.9426 45.8826 11.407C45.3253 11.9643 44.6751 12.2429 43.8392 12.2429C43.0033 12.2429 42.3531 11.9643 41.7959 11.407C41.2386 10.8497 40.96 10.1996 40.96 9.36364C40.96 8.52772 41.2386 7.87757 41.7959 7.32029C42.3531 6.76301 43.0033 6.48438 43.8392 6.48438C44.2107 6.48438 44.5823 6.57725 44.9538 6.76301C45.3253 6.94877 45.6039 7.13453 45.7897 7.41317L45.3253 7.87757C44.9538 7.41317 44.4894 7.22741 43.8392 7.22741C43.2819 7.22741 42.7247 7.41317 42.3531 7.87757C41.8888 8.24909 41.703 8.80636 41.703 9.45652C41.703 10.1067 41.8888 10.664 42.3531 11.0355C42.8175 11.407 43.2819 11.6856 43.8392 11.6856C44.4894 11.6856 44.9538 11.4999 45.4182 11.0355C45.6968 10.7568 45.8826 10.3853 45.8826 9.92092H43.8392V9.17788H46.5327V9.5494ZM50.8052 7.22741H48.2974V8.99212H50.6194V9.64228H48.2974V11.407H50.8052V12.15H47.5544V6.57725H50.8052V7.22741ZM53.8702 12.15H53.1271V7.22741H51.5482V6.57725H55.4491V7.22741H53.8702V12.15ZM58.1426 12.15V6.57725H58.8857V12.15H58.1426ZM62.0436 12.15H61.3005V7.22741H59.7216V6.57725H63.5296V7.22741H61.9507V12.15H62.0436ZM70.8671 11.407C70.3098 11.9643 69.6597 12.2429 68.8237 12.2429C67.9878 12.2429 67.3377 11.9643 66.7804 11.407C66.2231 10.8497 65.9445 10.1996 65.9445 9.36364C65.9445 8.52772 66.2231 7.87757 66.7804 7.32029C67.3377 6.76301 67.9878 6.48438 68.8237 6.48438C69.6597 6.48438 70.3098 6.76301 70.8671 7.32029C71.4244 7.87757 71.703 8.52772 71.703 9.36364C71.703 10.1996 71.4244 10.8497 70.8671 11.407ZM67.3377 10.9426C67.7092 11.3141 68.2665 11.5927 68.8237 11.5927C69.381 11.5927 69.9383 11.407 70.3098 10.9426C70.6813 10.5711 70.96 10.0138 70.96 9.36364C70.96 8.71348 70.7742 8.15621 70.3098 7.78469C69.9383 7.41317 69.381 7.13453 68.8237 7.13453C68.2665 7.13453 67.7092 7.32029 67.3377 7.78469C66.9662 8.15621 66.6875 8.71348 66.6875 9.36364C66.6875 10.0138 66.8733 10.5711 67.3377 10.9426ZM72.7247 12.15V6.57725H73.5606L76.2541 10.9426V6.57725H76.9971V12.15H76.2541L73.3748 7.59893V12.15H72.7247Z" fill="white" stroke="white" stroke-width="0.187135" stroke-miterlimit="10"/>
</g>
<defs>
<clipPath id="clip0_591_70869">
<rect width="135" height="40" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 7.5 KiB

46
ui-ngx/src/assets/android-ios-stores-badges/ios_store_en_black_badge.svg

@ -0,0 +1,46 @@
<svg id="livetype" xmlns="http://www.w3.org/2000/svg" width="119.66407" height="40" viewBox="0 0 119.66407 40">
<title>Download_on_the_App_Store_Badge_US-UK_RGB_blk_4SVG_092917</title>
<g>
<g>
<g>
<path d="M110.13477,0H9.53468c-.3667,0-.729,0-1.09473.002-.30615.002-.60986.00781-.91895.0127A13.21476,13.21476,0,0,0,5.5171.19141a6.66509,6.66509,0,0,0-1.90088.627A6.43779,6.43779,0,0,0,1.99757,1.99707,6.25844,6.25844,0,0,0,.81935,3.61816a6.60119,6.60119,0,0,0-.625,1.90332,12.993,12.993,0,0,0-.1792,2.002C.00587,7.83008.00489,8.1377,0,8.44434V31.5586c.00489.3105.00587.6113.01515.9219a12.99232,12.99232,0,0,0,.1792,2.0019,6.58756,6.58756,0,0,0,.625,1.9043A6.20778,6.20778,0,0,0,1.99757,38.001a6.27445,6.27445,0,0,0,1.61865,1.1787,6.70082,6.70082,0,0,0,1.90088.6308,13.45514,13.45514,0,0,0,2.0039.1768c.30909.0068.6128.0107.91895.0107C8.80567,40,9.168,40,9.53468,40H110.13477c.3594,0,.7246,0,1.084-.002.3047,0,.6172-.0039.9219-.0107a13.279,13.279,0,0,0,2-.1768,6.80432,6.80432,0,0,0,1.9082-.6308,6.27742,6.27742,0,0,0,1.6172-1.1787,6.39482,6.39482,0,0,0,1.1816-1.6143,6.60413,6.60413,0,0,0,.6191-1.9043,13.50643,13.50643,0,0,0,.1856-2.0019c.0039-.3106.0039-.6114.0039-.9219.0078-.3633.0078-.7246.0078-1.0938V9.53613c0-.36621,0-.72949-.0078-1.09179,0-.30664,0-.61426-.0039-.9209a13.5071,13.5071,0,0,0-.1856-2.002,6.6177,6.6177,0,0,0-.6191-1.90332,6.46619,6.46619,0,0,0-2.7988-2.7998,6.76754,6.76754,0,0,0-1.9082-.627,13.04394,13.04394,0,0,0-2-.17676c-.3047-.00488-.6172-.01074-.9219-.01269-.3594-.002-.7246-.002-1.084-.002Z" style="fill: #a6a6a6"/>
<path d="M8.44483,39.125c-.30468,0-.602-.0039-.90429-.0107a12.68714,12.68714,0,0,1-1.86914-.1631,5.88381,5.88381,0,0,1-1.65674-.5479,5.40573,5.40573,0,0,1-1.397-1.0166,5.32082,5.32082,0,0,1-1.02051-1.3965,5.72186,5.72186,0,0,1-.543-1.6572,12.41351,12.41351,0,0,1-.1665-1.875c-.00634-.2109-.01464-.9131-.01464-.9131V8.44434S.88185,7.75293.8877,7.5498a12.37039,12.37039,0,0,1,.16553-1.87207,5.7555,5.7555,0,0,1,.54346-1.6621A5.37349,5.37349,0,0,1,2.61183,2.61768,5.56543,5.56543,0,0,1,4.01417,1.59521a5.82309,5.82309,0,0,1,1.65332-.54394A12.58589,12.58589,0,0,1,7.543.88721L8.44532.875H111.21387l.9131.0127a12.38493,12.38493,0,0,1,1.8584.16259,5.93833,5.93833,0,0,1,1.6709.54785,5.59374,5.59374,0,0,1,2.415,2.41993,5.76267,5.76267,0,0,1,.5352,1.64892,12.995,12.995,0,0,1,.1738,1.88721c.0029.2832.0029.5874.0029.89014.0079.375.0079.73193.0079,1.09179V30.4648c0,.3633,0,.7178-.0079,1.0752,0,.3252,0,.6231-.0039.9297a12.73126,12.73126,0,0,1-.1709,1.8535,5.739,5.739,0,0,1-.54,1.67,5.48029,5.48029,0,0,1-1.0156,1.3857,5.4129,5.4129,0,0,1-1.3994,1.0225,5.86168,5.86168,0,0,1-1.668.5498,12.54218,12.54218,0,0,1-1.8692.1631c-.2929.0068-.5996.0107-.8974.0107l-1.084.002Z"/>
</g>
<g id="_Group_" data-name="&lt;Group&gt;">
<g id="_Group_2" data-name="&lt;Group&gt;">
<g id="_Group_3" data-name="&lt;Group&gt;">
<path id="_Path_" data-name="&lt;Path&gt;" d="M24.76888,20.30068a4.94881,4.94881,0,0,1,2.35656-4.15206,5.06566,5.06566,0,0,0-3.99116-2.15768c-1.67924-.17626-3.30719,1.00483-4.1629,1.00483-.87227,0-2.18977-.98733-3.6085-.95814a5.31529,5.31529,0,0,0-4.47292,2.72787c-1.934,3.34842-.49141,8.26947,1.3612,10.97608.9269,1.32535,2.01018,2.8058,3.42763,2.7533,1.38706-.05753,1.9051-.88448,3.5794-.88448,1.65876,0,2.14479.88448,3.591.8511,1.48838-.02416,2.42613-1.33124,3.32051-2.66914a10.962,10.962,0,0,0,1.51842-3.09251A4.78205,4.78205,0,0,1,24.76888,20.30068Z" style="fill: #fff"/>
<path id="_Path_2" data-name="&lt;Path&gt;" d="M22.03725,12.21089a4.87248,4.87248,0,0,0,1.11452-3.49062,4.95746,4.95746,0,0,0-3.20758,1.65961,4.63634,4.63634,0,0,0-1.14371,3.36139A4.09905,4.09905,0,0,0,22.03725,12.21089Z" style="fill: #fff"/>
</g>
</g>
<g>
<path d="M42.30227,27.13965h-4.7334l-1.13672,3.35645H34.42727l4.4834-12.418h2.083l4.4834,12.418H43.438ZM38.0591,25.59082h3.752l-1.84961-5.44727h-.05176Z" style="fill: #fff"/>
<path d="M55.15969,25.96973c0,2.81348-1.50586,4.62109-3.77832,4.62109a3.0693,3.0693,0,0,1-2.84863-1.584h-.043v4.48438h-1.8584V21.44238H48.4302v1.50586h.03418a3.21162,3.21162,0,0,1,2.88281-1.60059C53.645,21.34766,55.15969,23.16406,55.15969,25.96973Zm-1.91016,0c0-1.833-.94727-3.03809-2.39258-3.03809-1.41992,0-2.375,1.23047-2.375,3.03809,0,1.82422.95508,3.0459,2.375,3.0459C52.30227,29.01563,53.24953,27.81934,53.24953,25.96973Z" style="fill: #fff"/>
<path d="M65.12453,25.96973c0,2.81348-1.50586,4.62109-3.77832,4.62109a3.0693,3.0693,0,0,1-2.84863-1.584h-.043v4.48438h-1.8584V21.44238H58.395v1.50586h.03418A3.21162,3.21162,0,0,1,61.312,21.34766C63.60988,21.34766,65.12453,23.16406,65.12453,25.96973Zm-1.91016,0c0-1.833-.94727-3.03809-2.39258-3.03809-1.41992,0-2.375,1.23047-2.375,3.03809,0,1.82422.95508,3.0459,2.375,3.0459C62.26711,29.01563,63.21438,27.81934,63.21438,25.96973Z" style="fill: #fff"/>
<path d="M71.71047,27.03613c.1377,1.23145,1.334,2.04,2.96875,2.04,1.56641,0,2.69336-.80859,2.69336-1.91895,0-.96387-.67969-1.541-2.28906-1.93652l-1.60937-.3877c-2.28027-.55078-3.33887-1.61719-3.33887-3.34766,0-2.14258,1.86719-3.61426,4.51855-3.61426,2.624,0,4.42285,1.47168,4.4834,3.61426h-1.876c-.1123-1.23926-1.13672-1.9873-2.63379-1.9873s-2.52148.75684-2.52148,1.8584c0,.87793.6543,1.39453,2.25488,1.79l1.36816.33594c2.54785.60254,3.60645,1.626,3.60645,3.44238,0,2.32324-1.85059,3.77832-4.79395,3.77832-2.75391,0-4.61328-1.4209-4.7334-3.667Z" style="fill: #fff"/>
<path d="M83.34621,19.2998v2.14258h1.72168v1.47168H83.34621v4.99121c0,.77539.34473,1.13672,1.10156,1.13672a5.80752,5.80752,0,0,0,.61133-.043v1.46289a5.10351,5.10351,0,0,1-1.03223.08594c-1.833,0-2.54785-.68848-2.54785-2.44434V22.91406H80.16262V21.44238H81.479V19.2998Z" style="fill: #fff"/>
<path d="M86.065,25.96973c0-2.84863,1.67773-4.63867,4.29395-4.63867,2.625,0,4.29492,1.79,4.29492,4.63867,0,2.85645-1.66113,4.63867-4.29492,4.63867C87.72609,30.6084,86.065,28.82617,86.065,25.96973Zm6.69531,0c0-1.9541-.89551-3.10742-2.40137-3.10742s-2.40039,1.16211-2.40039,3.10742c0,1.96191.89453,3.10645,2.40039,3.10645S92.76027,27.93164,92.76027,25.96973Z" style="fill: #fff"/>
<path d="M96.18606,21.44238h1.77246v1.541h.043a2.1594,2.1594,0,0,1,2.17773-1.63574,2.86616,2.86616,0,0,1,.63672.06934v1.73828a2.59794,2.59794,0,0,0-.835-.1123,1.87264,1.87264,0,0,0-1.93652,2.083v5.37012h-1.8584Z" style="fill: #fff"/>
<path d="M109.3843,27.83691c-.25,1.64355-1.85059,2.77148-3.89844,2.77148-2.63379,0-4.26855-1.76465-4.26855-4.5957,0-2.83984,1.64355-4.68164,4.19043-4.68164,2.50488,0,4.08008,1.7207,4.08008,4.46582v.63672h-6.39453v.1123a2.358,2.358,0,0,0,2.43555,2.56445,2.04834,2.04834,0,0,0,2.09082-1.27344Zm-6.28223-2.70215h4.52637a2.1773,2.1773,0,0,0-2.2207-2.29785A2.292,2.292,0,0,0,103.10207,25.13477Z" style="fill: #fff"/>
</g>
</g>
</g>
<g id="_Group_4" data-name="&lt;Group&gt;">
<g>
<path d="M37.82619,8.731a2.63964,2.63964,0,0,1,2.80762,2.96484c0,1.90625-1.03027,3.002-2.80762,3.002H35.67092V8.731Zm-1.22852,5.123h1.125a1.87588,1.87588,0,0,0,1.96777-2.146,1.881,1.881,0,0,0-1.96777-2.13379h-1.125Z" style="fill: #fff"/>
<path d="M41.68068,12.44434a2.13323,2.13323,0,1,1,4.24707,0,2.13358,2.13358,0,1,1-4.24707,0Zm3.333,0c0-.97607-.43848-1.54687-1.208-1.54687-.77246,0-1.207.5708-1.207,1.54688,0,.98389.43457,1.55029,1.207,1.55029C44.57522,13.99463,45.01369,13.42432,45.01369,12.44434Z" style="fill: #fff"/>
<path d="M51.57326,14.69775h-.92187l-.93066-3.31641h-.07031l-.92676,3.31641h-.91309l-1.24121-4.50293h.90137l.80664,3.436h.06641l.92578-3.436h.85254l.92578,3.436h.07031l.80273-3.436h.88867Z" style="fill: #fff"/>
<path d="M53.85354,10.19482H54.709v.71533h.06641a1.348,1.348,0,0,1,1.34375-.80225,1.46456,1.46456,0,0,1,1.55859,1.6748v2.915h-.88867V12.00586c0-.72363-.31445-1.0835-.97168-1.0835a1.03294,1.03294,0,0,0-1.0752,1.14111v2.63428h-.88867Z" style="fill: #fff"/>
<path d="M59.09377,8.437h.88867v6.26074h-.88867Z" style="fill: #fff"/>
<path d="M61.21779,12.44434a2.13346,2.13346,0,1,1,4.24756,0,2.1338,2.1338,0,1,1-4.24756,0Zm3.333,0c0-.97607-.43848-1.54687-1.208-1.54687-.77246,0-1.207.5708-1.207,1.54688,0,.98389.43457,1.55029,1.207,1.55029C64.11232,13.99463,64.5508,13.42432,64.5508,12.44434Z" style="fill: #fff"/>
<path d="M66.4009,13.42432c0-.81055.60352-1.27783,1.6748-1.34424l1.21973-.07031v-.38867c0-.47559-.31445-.74414-.92187-.74414-.49609,0-.83984.18213-.93848.50049h-.86035c.09082-.77344.81836-1.26953,1.83984-1.26953,1.12891,0,1.76563.562,1.76563,1.51318v3.07666h-.85547v-.63281h-.07031a1.515,1.515,0,0,1-1.35254.707A1.36026,1.36026,0,0,1,66.4009,13.42432Zm2.89453-.38477v-.37646l-1.09961.07031c-.62012.0415-.90137.25244-.90137.64941,0,.40527.35156.64111.835.64111A1.0615,1.0615,0,0,0,69.29543,13.03955Z" style="fill: #fff"/>
<path d="M71.34816,12.44434c0-1.42285.73145-2.32422,1.86914-2.32422a1.484,1.484,0,0,1,1.38086.79h.06641V8.437h.88867v6.26074h-.85156v-.71143h-.07031a1.56284,1.56284,0,0,1-1.41406.78564C72.0718,14.772,71.34816,13.87061,71.34816,12.44434Zm.918,0c0,.95508.4502,1.52979,1.20313,1.52979.749,0,1.21191-.583,1.21191-1.52588,0-.93848-.46777-1.52979-1.21191-1.52979C72.72121,10.91846,72.26613,11.49707,72.26613,12.44434Z" style="fill: #fff"/>
<path d="M79.23,12.44434a2.13323,2.13323,0,1,1,4.24707,0,2.13358,2.13358,0,1,1-4.24707,0Zm3.333,0c0-.97607-.43848-1.54687-1.208-1.54687-.77246,0-1.207.5708-1.207,1.54688,0,.98389.43457,1.55029,1.207,1.55029C82.12453,13.99463,82.563,13.42432,82.563,12.44434Z" style="fill: #fff"/>
<path d="M84.66945,10.19482h.85547v.71533h.06641a1.348,1.348,0,0,1,1.34375-.80225,1.46456,1.46456,0,0,1,1.55859,1.6748v2.915H87.605V12.00586c0-.72363-.31445-1.0835-.97168-1.0835a1.03294,1.03294,0,0,0-1.0752,1.14111v2.63428h-.88867Z" style="fill: #fff"/>
<path d="M93.51516,9.07373v1.1416h.97559v.74854h-.97559V13.2793c0,.47168.19434.67822.63672.67822a2.96657,2.96657,0,0,0,.33887-.02051v.74023a2.9155,2.9155,0,0,1-.4834.04541c-.98828,0-1.38184-.34766-1.38184-1.21582v-2.543h-.71484v-.74854h.71484V9.07373Z" style="fill: #fff"/>
<path d="M95.70461,8.437h.88086v2.48145h.07031a1.3856,1.3856,0,0,1,1.373-.80664,1.48339,1.48339,0,0,1,1.55078,1.67871v2.90723H98.69v-2.688c0-.71924-.335-1.0835-.96289-1.0835a1.05194,1.05194,0,0,0-1.13379,1.1416v2.62988h-.88867Z" style="fill: #fff"/>
<path d="M104.76125,13.48193a1.828,1.828,0,0,1-1.95117,1.30273A2.04531,2.04531,0,0,1,100.73,12.46045a2.07685,2.07685,0,0,1,2.07617-2.35254c1.25293,0,2.00879.856,2.00879,2.27V12.688h-3.17969v.0498a1.1902,1.1902,0,0,0,1.19922,1.29,1.07934,1.07934,0,0,0,1.07129-.5459Zm-3.126-1.45117h2.27441a1.08647,1.08647,0,0,0-1.1084-1.1665A1.15162,1.15162,0,0,0,101.63527,12.03076Z" style="fill: #fff"/>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 11 KiB

46
ui-ngx/src/assets/android-ios-stores-badges/ios_store_en_white_badge.svg

@ -0,0 +1,46 @@
<svg id="livetype" xmlns="http://www.w3.org/2000/svg" width="119.66407" height="40" viewBox="0 0 119.66407 40">
<title>Download_on_the_App_Store_Badge_US-UK_RGB_wht_092917</title>
<g>
<g>
<g>
<path d="M110.13477,0H9.53468c-.3667,0-.729,0-1.09473.002-.30615.002-.60986.00781-.91895.0127A13.21476,13.21476,0,0,0,5.5171.19141a6.66509,6.66509,0,0,0-1.90088.627A6.43779,6.43779,0,0,0,1.99757,1.99707,6.25844,6.25844,0,0,0,.81935,3.61816a6.60119,6.60119,0,0,0-.625,1.90332,12.993,12.993,0,0,0-.1792,2.002C.00587,7.83008.00489,8.1377,0,8.44434V31.5586c.00489.3105.00587.6113.01515.9219a12.99232,12.99232,0,0,0,.1792,2.0019,6.58756,6.58756,0,0,0,.625,1.9043A6.20778,6.20778,0,0,0,1.99757,38.001a6.27445,6.27445,0,0,0,1.61865,1.1787,6.70082,6.70082,0,0,0,1.90088.6308,13.45514,13.45514,0,0,0,2.0039.1768c.30909.0068.6128.0107.91895.0107C8.80567,40,9.168,40,9.53468,40H110.13477c.3594,0,.7246,0,1.084-.002.3047,0,.6172-.0039.9219-.0107a13.279,13.279,0,0,0,2-.1768,6.80432,6.80432,0,0,0,1.9082-.6308,6.27742,6.27742,0,0,0,1.6172-1.1787,6.39482,6.39482,0,0,0,1.1816-1.6143,6.60413,6.60413,0,0,0,.6191-1.9043,13.50643,13.50643,0,0,0,.1856-2.0019c.0039-.3106.0039-.6114.0039-.9219.0078-.3633.0078-.7246.0078-1.0938V9.53613c0-.36621,0-.72949-.0078-1.09179,0-.30664,0-.61426-.0039-.9209a13.5071,13.5071,0,0,0-.1856-2.002,6.6177,6.6177,0,0,0-.6191-1.90332,6.46619,6.46619,0,0,0-2.7988-2.7998,6.76754,6.76754,0,0,0-1.9082-.627,13.04394,13.04394,0,0,0-2-.17676c-.3047-.00488-.6172-.01074-.9219-.01269-.3594-.002-.7246-.002-1.084-.002Z"/>
<path d="M8.44483,39.125c-.30468,0-.602-.0039-.90429-.0107a12.68714,12.68714,0,0,1-1.86914-.1631,5.88381,5.88381,0,0,1-1.65674-.5479,5.40573,5.40573,0,0,1-1.397-1.0166,5.32082,5.32082,0,0,1-1.02051-1.3965,5.72186,5.72186,0,0,1-.543-1.6572,12.41351,12.41351,0,0,1-.1665-1.875c-.00634-.2109-.01464-.9131-.01464-.9131V8.44434S.88185,7.75293.8877,7.5498a12.37039,12.37039,0,0,1,.16553-1.87207,5.7555,5.7555,0,0,1,.54346-1.6621A5.37349,5.37349,0,0,1,2.61183,2.61768,5.56543,5.56543,0,0,1,4.01417,1.59521a5.82309,5.82309,0,0,1,1.65332-.54394A12.58589,12.58589,0,0,1,7.543.88721L8.44532.875H111.21387l.9131.0127a12.38493,12.38493,0,0,1,1.8584.16259,5.93833,5.93833,0,0,1,1.6709.54785,5.59374,5.59374,0,0,1,2.415,2.41993,5.76267,5.76267,0,0,1,.5352,1.64892,12.995,12.995,0,0,1,.1738,1.88721c.0029.2832.0029.5874.0029.89014.0079.375.0079.73193.0079,1.09179V30.4648c0,.3633,0,.7178-.0079,1.0752,0,.3252,0,.6231-.0039.9297a12.73126,12.73126,0,0,1-.1709,1.8535,5.739,5.739,0,0,1-.54,1.67,5.48029,5.48029,0,0,1-1.0156,1.3857,5.4129,5.4129,0,0,1-1.3994,1.0225,5.86168,5.86168,0,0,1-1.668.5498,12.54218,12.54218,0,0,1-1.8692.1631c-.2929.0068-.5996.0107-.8974.0107l-1.084.002Z" style="fill: #fff"/>
</g>
<g id="_Group_" data-name="&lt;Group&gt;">
<g id="_Group_2" data-name="&lt;Group&gt;">
<g id="_Group_3" data-name="&lt;Group&gt;">
<path id="_Path_" data-name="&lt;Path&gt;" d="M24.99671,19.88935a5.14625,5.14625,0,0,1,2.45058-4.31771,5.26776,5.26776,0,0,0-4.15039-2.24376c-1.74624-.1833-3.43913,1.04492-4.329,1.04492-.90707,0-2.27713-1.02672-3.75247-.99637a5.52735,5.52735,0,0,0-4.65137,2.8367c-2.01111,3.482-.511,8.59939,1.41551,11.414.96388,1.37823,2.09037,2.91774,3.56438,2.86315,1.4424-.05983,1.98111-.91977,3.7222-.91977,1.72494,0,2.23035.91977,3.73427.88506,1.54777-.02512,2.52292-1.38435,3.453-2.77563a11.39931,11.39931,0,0,0,1.579-3.21589A4.97284,4.97284,0,0,1,24.99671,19.88935Z"/>
<path id="_Path_2" data-name="&lt;Path&gt;" d="M22.15611,11.47681a5.06687,5.06687,0,0,0,1.159-3.62989,5.15524,5.15524,0,0,0-3.33555,1.72582,4.82131,4.82131,0,0,0-1.18934,3.4955A4.26259,4.26259,0,0,0,22.15611,11.47681Z"/>
</g>
</g>
<g>
<path d="M42.30178,27.13965h-4.7334l-1.13672,3.35645H34.42678l4.4834-12.418h2.083l4.4834,12.418H43.43752Zm-4.24316-1.54883h3.752L39.961,20.14355H39.9092Z"/>
<path d="M55.1592,25.96973c0,2.81348-1.50586,4.62109-3.77832,4.62109a3.0693,3.0693,0,0,1-2.84863-1.584h-.043v4.48438h-1.8584V21.44238h1.79883v1.50586h.03418a3.21162,3.21162,0,0,1,2.88281-1.60059C53.64455,21.34766,55.1592,23.16406,55.1592,25.96973Zm-1.91016,0c0-1.833-.94727-3.03809-2.39258-3.03809-1.41992,0-2.375,1.23047-2.375,3.03809,0,1.82422.95508,3.0459,2.375,3.0459C52.30178,29.01563,53.249,27.81934,53.249,25.96973Z"/>
<path d="M65.12453,25.96973c0,2.81348-1.50635,4.62109-3.77881,4.62109a3.0693,3.0693,0,0,1-2.84863-1.584h-.043v4.48438h-1.8584V21.44238h1.79883v1.50586h.03418a3.21162,3.21162,0,0,1,2.88281-1.60059C63.6094,21.34766,65.12453,23.16406,65.12453,25.96973Zm-1.91064,0c0-1.833-.94727-3.03809-2.39258-3.03809-1.41992,0-2.375,1.23047-2.375,3.03809,0,1.82422.95508,3.0459,2.375,3.0459C62.26662,29.01563,63.21389,27.81934,63.21389,25.96973Z"/>
<path d="M71.70949,27.03613c.1377,1.23145,1.334,2.04,2.96875,2.04,1.56641,0,2.69336-.80859,2.69336-1.91895,0-.96387-.67969-1.541-2.28906-1.93652l-1.60937-.3877c-2.28027-.55078-3.33887-1.61719-3.33887-3.34766,0-2.14258,1.86719-3.61426,4.51758-3.61426,2.625,0,4.42383,1.47168,4.48438,3.61426h-1.876c-.1123-1.23926-1.13672-1.9873-2.63379-1.9873s-2.52148.75684-2.52148,1.8584c0,.87793.6543,1.39453,2.25488,1.79l1.36816.33594c2.54785.60254,3.60547,1.626,3.60547,3.44238,0,2.32324-1.84961,3.77832-4.793,3.77832-2.75391,0-4.61328-1.4209-4.7334-3.667Z"/>
<path d="M83.34621,19.2998v2.14258h1.72168v1.47168H83.34621v4.99121c0,.77539.34473,1.13672,1.10156,1.13672a5.80752,5.80752,0,0,0,.61133-.043v1.46289a5.10351,5.10351,0,0,1-1.03223.08594c-1.833,0-2.54785-.68848-2.54785-2.44434V22.91406H80.16262V21.44238H81.479V19.2998Z"/>
<path d="M86.064,25.96973c0-2.84863,1.67773-4.63867,4.29395-4.63867,2.625,0,4.29492,1.79,4.29492,4.63867,0,2.85645-1.66113,4.63867-4.29492,4.63867C87.72512,30.6084,86.064,28.82617,86.064,25.96973Zm6.69531,0c0-1.9541-.89551-3.10742-2.40137-3.10742s-2.40137,1.16211-2.40137,3.10742c0,1.96191.89551,3.10645,2.40137,3.10645S92.7593,27.93164,92.7593,25.96973Z"/>
<path d="M96.18508,21.44238h1.77246v1.541h.043a2.1594,2.1594,0,0,1,2.17773-1.63574,2.86616,2.86616,0,0,1,.63672.06934v1.73828a2.59794,2.59794,0,0,0-.835-.1123,1.87264,1.87264,0,0,0-1.93652,2.083v5.37012h-1.8584Z"/>
<path d="M109.38332,27.83691c-.25,1.64355-1.85059,2.77148-3.89844,2.77148-2.63379,0-4.26855-1.76465-4.26855-4.5957,0-2.83984,1.64355-4.68164,4.19043-4.68164,2.50488,0,4.08008,1.7207,4.08008,4.46582v.63672h-6.39453v.1123a2.358,2.358,0,0,0,2.43555,2.56445,2.04834,2.04834,0,0,0,2.09082-1.27344Zm-6.28223-2.70215h4.52637a2.1773,2.1773,0,0,0-2.2207-2.29785A2.292,2.292,0,0,0,103.10109,25.13477Z"/>
</g>
</g>
</g>
<g id="_Group_4" data-name="&lt;Group&gt;">
<g>
<path d="M37.82619,8.731a2.63964,2.63964,0,0,1,2.80762,2.96484c0,1.90625-1.03027,3.002-2.80762,3.002H35.67092V8.731Zm-1.22852,5.123h1.125a1.87588,1.87588,0,0,0,1.96777-2.146,1.881,1.881,0,0,0-1.96777-2.13379h-1.125Z"/>
<path d="M41.68068,12.44434a2.13323,2.13323,0,1,1,4.24707,0,2.13358,2.13358,0,1,1-4.24707,0Zm3.333,0c0-.97607-.43848-1.54687-1.208-1.54687-.77246,0-1.207.5708-1.207,1.54688,0,.98389.43457,1.55029,1.207,1.55029C44.57522,13.99463,45.01369,13.42432,45.01369,12.44434Z"/>
<path d="M51.57326,14.69775h-.92187l-.93066-3.31641h-.07031l-.92676,3.31641h-.91309l-1.24121-4.50293h.90137l.80664,3.436h.06641l.92578-3.436h.85254l.92578,3.436h.07031l.80273-3.436h.88867Z"/>
<path d="M53.85354,10.19482H54.709v.71533h.06641a1.348,1.348,0,0,1,1.34375-.80225,1.46456,1.46456,0,0,1,1.55859,1.6748v2.915h-.88867V12.00586c0-.72363-.31445-1.0835-.97168-1.0835a1.03294,1.03294,0,0,0-1.0752,1.14111v2.63428h-.88867Z"/>
<path d="M59.09377,8.437h.88867v6.26074h-.88867Z"/>
<path d="M61.21779,12.44434a2.13346,2.13346,0,1,1,4.24756,0,2.1338,2.1338,0,1,1-4.24756,0Zm3.333,0c0-.97607-.43848-1.54687-1.208-1.54687-.77246,0-1.207.5708-1.207,1.54688,0,.98389.43457,1.55029,1.207,1.55029C64.11232,13.99463,64.5508,13.42432,64.5508,12.44434Z"/>
<path d="M66.4009,13.42432c0-.81055.60352-1.27783,1.6748-1.34424l1.21973-.07031v-.38867c0-.47559-.31445-.74414-.92187-.74414-.49609,0-.83984.18213-.93848.50049h-.86035c.09082-.77344.81836-1.26953,1.83984-1.26953,1.12891,0,1.76563.562,1.76563,1.51318v3.07666h-.85547v-.63281h-.07031a1.515,1.515,0,0,1-1.35254.707A1.36026,1.36026,0,0,1,66.4009,13.42432Zm2.89453-.38477v-.37646l-1.09961.07031c-.62012.0415-.90137.25244-.90137.64941,0,.40527.35156.64111.835.64111A1.0615,1.0615,0,0,0,69.29543,13.03955Z"/>
<path d="M71.34816,12.44434c0-1.42285.73145-2.32422,1.86914-2.32422a1.484,1.484,0,0,1,1.38086.79h.06641V8.437h.88867v6.26074h-.85156v-.71143h-.07031a1.56284,1.56284,0,0,1-1.41406.78564C72.0718,14.772,71.34816,13.87061,71.34816,12.44434Zm.918,0c0,.95508.4502,1.52979,1.20313,1.52979.749,0,1.21191-.583,1.21191-1.52588,0-.93848-.46777-1.52979-1.21191-1.52979C72.72121,10.91846,72.26613,11.49707,72.26613,12.44434Z"/>
<path d="M79.23,12.44434a2.13323,2.13323,0,1,1,4.24707,0,2.13358,2.13358,0,1,1-4.24707,0Zm3.333,0c0-.97607-.43848-1.54687-1.208-1.54687-.77246,0-1.207.5708-1.207,1.54688,0,.98389.43457,1.55029,1.207,1.55029C82.12453,13.99463,82.563,13.42432,82.563,12.44434Z"/>
<path d="M84.66945,10.19482h.85547v.71533h.06641a1.348,1.348,0,0,1,1.34375-.80225,1.46456,1.46456,0,0,1,1.55859,1.6748v2.915H87.605V12.00586c0-.72363-.31445-1.0835-.97168-1.0835a1.03294,1.03294,0,0,0-1.0752,1.14111v2.63428h-.88867Z"/>
<path d="M93.51516,9.07373v1.1416h.97559v.74854h-.97559V13.2793c0,.47168.19434.67822.63672.67822a2.96657,2.96657,0,0,0,.33887-.02051v.74023a2.9155,2.9155,0,0,1-.4834.04541c-.98828,0-1.38184-.34766-1.38184-1.21582v-2.543h-.71484v-.74854h.71484V9.07373Z"/>
<path d="M95.70461,8.437h.88086v2.48145h.07031a1.3856,1.3856,0,0,1,1.373-.80664,1.48339,1.48339,0,0,1,1.55078,1.67871v2.90723H98.69v-2.688c0-.71924-.335-1.0835-.96289-1.0835a1.05194,1.05194,0,0,0-1.13379,1.1416v2.62988h-.88867Z"/>
<path d="M104.76125,13.48193a1.828,1.828,0,0,1-1.95117,1.30273A2.04531,2.04531,0,0,1,100.73,12.46045a2.07685,2.07685,0,0,1,2.07617-2.35254c1.25293,0,2.00879.856,2.00879,2.27V12.688h-3.17969v.0498a1.1902,1.1902,0,0,0,1.19922,1.29,1.07934,1.07934,0,0,0,1.07129-.5459Zm-3.126-1.45117h2.27441a1.08647,1.08647,0,0,0-1.1084-1.1665A1.15162,1.15162,0,0,0,101.63527,12.03076Z"/>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 10 KiB

53
ui-ngx/src/assets/dashboard/tenant_admin_home_page.json

@ -1143,6 +1143,49 @@
"row": 0,
"col": 0,
"id": "8e71a398-caf5-540d-cec5-6e5dc264343e"
},
"b9d704a2-f579-dbff-f123-f953d92081fd": {
"typeFullFqn": "system.home_page_widgets.mobile_app_qr_code",
"type": "static",
"sizeX": 6,
"sizeY": 3,
"config": {
"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": false
},
"row": 0,
"col": 0,
"id": "b9d704a2-f579-dbff-f123-f953d92081fd"
}
},
"states": {
@ -1169,7 +1212,7 @@
},
"a23185ad-dc46-806c-0e50-5b21fb080ace": {
"sizeX": 35,
"sizeY": 58,
"sizeY": 42,
"row": 0,
"col": 85,
"mobileHide": true
@ -1212,6 +1255,12 @@
"row": 0,
"col": 43,
"mobileOrder": 0
},
"b9d704a2-f579-dbff-f123-f953d92081fd": {
"sizeX": 35,
"sizeY": 16,
"row": 42,
"col": 85
}
},
"gridSettings": {
@ -1416,4 +1465,4 @@
}
},
"name": "Tenant Administrator Home Page"
}
}

27
ui-ngx/src/assets/locale/locale.constant-en_US.json

@ -419,6 +419,33 @@
"no-auto-commit-entities-prompt": "No entities configured for auto-commit",
"delete-auto-commit-settings-title": "Are you sure you want to delete auto-commit settings?",
"delete-auto-commit-settings-text": "Be careful, after the confirmation the auto-commit settings will be removed and auto-commit will be disabled for all entities.",
"mobile-app": {
"mobile-app": "Mobile app",
"mobile-app-qr-code-widget-settings": "Mobile app QR code widget settings",
"applications": "Applications",
"default": "Default",
"custom": "Custom",
"android": "Android",
"app-package-name": "App package name",
"app-package-name-required": "App package name is required",
"sha256-certificate-fingerprints": "SHA256 certificate fingerprints",
"sha256-certificate-fingerprints-required": "SHA256 certificate fingerprints is required",
"ios": "iOS",
"app-id": "App ID",
"app-id-required": "App ID is required",
"appearance-on-home-page": "Appearance on Home page",
"enabled": "Enabled",
"disabled": "Disabled",
"badges": "Badges",
"original": "Original",
"white": "White",
"label": "Label",
"right": "Right",
"left": "Left",
"set": "Set",
"preview": "Preview",
"connect-mobile-app": "Connect mobile app"
},
"2fa": {
"2fa": "Two-factor authentication",
"available-providers": "Available providers",

15
ui-ngx/src/form.scss

@ -257,9 +257,15 @@
&.row {
flex-direction: row;
}
&.row-reverse {
flex-direction: row-reverse;
}
&.column {
flex-direction: column;
}
&.center {
justify-content: center;
}
&.flex-start {
justify-content: flex-start;
}
@ -278,6 +284,15 @@
&.fill-width {
width: 100%;
}
&.fill-height {
height: 100%;
}
&.shrink {
flex: 0;
}
&.no-gap {
gap: 0;
}
}
.tb-form-panel, .tb-form-row {

Loading…
Cancel
Save