18 changed files with 1186 additions and 20 deletions
@ -0,0 +1,70 @@ |
|||||
|
///
|
||||
|
/// Copyright © 2016-2021 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 { Inject, Injectable } from '@angular/core'; |
||||
|
import { WINDOW } from '@core/services/window.service'; |
||||
|
import { isDefined } from '@core/utils'; |
||||
|
import { MobileActionResult, WidgetMobileActionResult, WidgetMobileActionType } from '@shared/models/widget.models'; |
||||
|
import { from, of } from 'rxjs'; |
||||
|
import { Observable } from 'rxjs/internal/Observable'; |
||||
|
import { catchError } from 'rxjs/operators'; |
||||
|
|
||||
|
const dashboardStateNameHandler = 'tbMobileDashboardStateNameHandler'; |
||||
|
const mobileHandler = 'tbMobileHandler'; |
||||
|
|
||||
|
// @dynamic
|
||||
|
@Injectable({ |
||||
|
providedIn: 'root' |
||||
|
}) |
||||
|
export class MobileService { |
||||
|
|
||||
|
private readonly mobileApp; |
||||
|
private readonly mobileChannel; |
||||
|
|
||||
|
constructor(@Inject(WINDOW) private window: Window) { |
||||
|
const w = (this.window as any); |
||||
|
this.mobileChannel = w.flutter_inappwebview; |
||||
|
this.mobileApp = isDefined(this.mobileChannel); |
||||
|
} |
||||
|
|
||||
|
public isMobileApp(): boolean { |
||||
|
return this.mobileApp; |
||||
|
} |
||||
|
|
||||
|
public handleDashboardStateName(name: string) { |
||||
|
if (this.mobileApp) { |
||||
|
this.mobileChannel.callHandler(dashboardStateNameHandler, name); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public handleWidgetMobileAction<T extends MobileActionResult>(type: WidgetMobileActionType, ...args: any[]): |
||||
|
Observable<WidgetMobileActionResult<T>> { |
||||
|
if (this.mobileApp) { |
||||
|
return from( |
||||
|
this.mobileChannel.callHandler(mobileHandler, type, ...args) as Promise<WidgetMobileActionResult<T>>).pipe( |
||||
|
catchError((err: Error) => { |
||||
|
return of({ |
||||
|
hasError: true, |
||||
|
error: err?.message ? err.message : `Failed to execute mobile action ${type}` |
||||
|
} as WidgetMobileActionResult<any>); |
||||
|
}) |
||||
|
); |
||||
|
} else { |
||||
|
return of(null); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,100 @@ |
|||||
|
<!-- |
||||
|
|
||||
|
Copyright © 2016-2021 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 [formGroup]="mobileActionFormGroup"> |
||||
|
<mat-form-field class="mat-block"> |
||||
|
<mat-label translate>widget-action.mobile.action-type</mat-label> |
||||
|
<mat-select required formControlName="type"> |
||||
|
<mat-option *ngFor="let actionType of mobileActionTypes" [value]="actionType"> |
||||
|
{{ mobileActionTypeTranslations.get(mobileActionType[actionType]) | translate }} |
||||
|
</mat-option> |
||||
|
</mat-select> |
||||
|
<mat-error *ngIf="mobileActionFormGroup.get('type').hasError('required')"> |
||||
|
{{ 'widget-action.mobile.action-type-required' | translate }} |
||||
|
</mat-error> |
||||
|
</mat-form-field> |
||||
|
<div [formGroup]="mobileActionTypeFormGroup" [ngSwitch]="mobileActionFormGroup.get('type').value"> |
||||
|
<ng-template [ngSwitchCase]="mobileActionFormGroup.get('type').value === mobileActionType.mapDirection || |
||||
|
mobileActionFormGroup.get('type').value === mobileActionType.mapLocation ? |
||||
|
mobileActionFormGroup.get('type').value : ''"> |
||||
|
<tb-js-func |
||||
|
formControlName="getLocationFunction" |
||||
|
functionName="getLocation" |
||||
|
[functionArgs]="['$event', 'widgetContext', 'entityId', 'entityName', 'additionalParams', 'entityLabel']" |
||||
|
[editorCompleter]="customActionEditorCompleter" |
||||
|
></tb-js-func> |
||||
|
</ng-template> |
||||
|
<ng-template [ngSwitchCase]="mobileActionType.makePhoneCall"> |
||||
|
<tb-js-func |
||||
|
formControlName="getPhoneNumberFunction" |
||||
|
functionName="getPhoneNumber" |
||||
|
[functionArgs]="['$event', 'widgetContext', 'entityId', 'entityName', 'additionalParams', 'entityLabel']" |
||||
|
[editorCompleter]="customActionEditorCompleter" |
||||
|
></tb-js-func> |
||||
|
</ng-template> |
||||
|
<ng-template [ngSwitchCase]="mobileActionFormGroup.get('type').value === mobileActionType.takePhoto || |
||||
|
mobileActionFormGroup.get('type').value === mobileActionType.takePictureFromGallery || |
||||
|
mobileActionFormGroup.get('type').value === mobileActionType.takeScreenshot ? |
||||
|
mobileActionFormGroup.get('type').value : ''"> |
||||
|
<tb-js-func |
||||
|
formControlName="processImageFunction" |
||||
|
functionName="processImage" |
||||
|
[functionArgs]="['imageUrl', '$event', 'widgetContext', 'entityId', 'entityName', 'additionalParams', 'entityLabel']" |
||||
|
[editorCompleter]="customActionEditorCompleter" |
||||
|
></tb-js-func> |
||||
|
</ng-template> |
||||
|
<ng-template [ngSwitchCase]="mobileActionType.scanQrCode"> |
||||
|
<tb-js-func |
||||
|
formControlName="processQrCodeFunction" |
||||
|
functionName="processQrCode" |
||||
|
[functionArgs]="['code', 'format', '$event', 'widgetContext', 'entityId', 'entityName', 'additionalParams', 'entityLabel']" |
||||
|
[editorCompleter]="customActionEditorCompleter" |
||||
|
></tb-js-func> |
||||
|
</ng-template> |
||||
|
<ng-template [ngSwitchCase]="mobileActionType.getLocation"> |
||||
|
<tb-js-func |
||||
|
formControlName="processLocationFunction" |
||||
|
functionName="processLocation" |
||||
|
[functionArgs]="['latitude', 'longitude', '$event', 'widgetContext', 'entityId', 'entityName', 'additionalParams', 'entityLabel']" |
||||
|
[editorCompleter]="customActionEditorCompleter" |
||||
|
></tb-js-func> |
||||
|
</ng-template> |
||||
|
<ng-template [ngSwitchCase]="mobileActionFormGroup.get('type').value === mobileActionType.mapDirection || |
||||
|
mobileActionFormGroup.get('type').value === mobileActionType.mapLocation || |
||||
|
mobileActionFormGroup.get('type').value === mobileActionType.makePhoneCall ? |
||||
|
mobileActionFormGroup.get('type').value : ''"> |
||||
|
<tb-js-func |
||||
|
formControlName="processLaunchResultFunction" |
||||
|
functionName="processLaunchResult" |
||||
|
[functionArgs]="['launched', '$event', 'widgetContext', 'entityId', 'entityName', 'additionalParams', 'entityLabel']" |
||||
|
[editorCompleter]="customActionEditorCompleter" |
||||
|
></tb-js-func> |
||||
|
</ng-template> |
||||
|
</div> |
||||
|
<tb-js-func *ngIf="mobileActionFormGroup.get('type').value" |
||||
|
formControlName="handleEmptyResultFunction" |
||||
|
functionName="handleEmptyResult" |
||||
|
[functionArgs]="['$event', 'widgetContext', 'entityId', 'entityName', 'additionalParams', 'entityLabel']" |
||||
|
[editorCompleter]="customActionEditorCompleter" |
||||
|
></tb-js-func> |
||||
|
<tb-js-func *ngIf="mobileActionFormGroup.get('type').value" |
||||
|
formControlName="handleErrorFunction" |
||||
|
functionName="handleError" |
||||
|
[functionArgs]="['error', '$event', 'widgetContext', 'entityId', 'entityName', 'additionalParams', 'entityLabel']" |
||||
|
[editorCompleter]="customActionEditorCompleter" |
||||
|
></tb-js-func> |
||||
|
</div> |
||||
@ -0,0 +1,253 @@ |
|||||
|
///
|
||||
|
/// Copyright © 2016-2021 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, forwardRef, Input, OnInit, QueryList, ViewChild, ViewChildren } from '@angular/core'; |
||||
|
import { ControlValueAccessor, FormBuilder, FormGroup, NG_VALUE_ACCESSOR, Validators } from '@angular/forms'; |
||||
|
import { Store } from '@ngrx/store'; |
||||
|
import { AppState } from '@app/core/core.state'; |
||||
|
import { coerceBooleanProperty } from '@angular/cdk/coercion'; |
||||
|
import { deepClone } from '@core/utils'; |
||||
|
import { |
||||
|
WidgetMobileActionDescriptor, |
||||
|
WidgetMobileActionType, widgetMobileActionTypeTranslationMap |
||||
|
} from '@shared/models/widget.models'; |
||||
|
import { CustomActionEditorCompleter } from '@home/components/widget/action/custom-action.models'; |
||||
|
import { JsFuncComponent } from '@shared/components/js-func.component'; |
||||
|
import { |
||||
|
getDefaultGetLocationFunction, getDefaultGetPhoneNumberFunction, |
||||
|
getDefaultHandleEmptyResultFunction, |
||||
|
getDefaultHandleErrorFunction, |
||||
|
getDefaultProcessImageFunction, |
||||
|
getDefaultProcessLaunchResultFunction, getDefaultProcessLocationFunction, |
||||
|
getDefaultProcessQrCodeFunction |
||||
|
} from '@home/components/widget/action/mobile-action-editor.models'; |
||||
|
|
||||
|
@Component({ |
||||
|
selector: 'tb-mobile-action-editor', |
||||
|
templateUrl: './mobile-action-editor.component.html', |
||||
|
styleUrls: [], |
||||
|
providers: [{ |
||||
|
provide: NG_VALUE_ACCESSOR, |
||||
|
useExisting: forwardRef(() => MobileActionEditorComponent), |
||||
|
multi: true |
||||
|
}] |
||||
|
}) |
||||
|
export class MobileActionEditorComponent implements ControlValueAccessor, OnInit { |
||||
|
|
||||
|
@ViewChildren(JsFuncComponent) jsFuncComponents: QueryList<JsFuncComponent>; |
||||
|
|
||||
|
mobileActionTypes = Object.keys(WidgetMobileActionType); |
||||
|
mobileActionTypeTranslations = widgetMobileActionTypeTranslationMap; |
||||
|
mobileActionType = WidgetMobileActionType; |
||||
|
|
||||
|
customActionEditorCompleter = CustomActionEditorCompleter; |
||||
|
|
||||
|
mobileActionFormGroup: FormGroup; |
||||
|
mobileActionTypeFormGroup: FormGroup; |
||||
|
|
||||
|
private requiredValue: boolean; |
||||
|
get required(): boolean { |
||||
|
return this.requiredValue; |
||||
|
} |
||||
|
@Input() |
||||
|
set required(value: boolean) { |
||||
|
this.requiredValue = coerceBooleanProperty(value); |
||||
|
} |
||||
|
|
||||
|
@Input() |
||||
|
disabled: boolean; |
||||
|
|
||||
|
private propagateChange = (v: any) => { }; |
||||
|
|
||||
|
constructor(private store: Store<AppState>, |
||||
|
private fb: FormBuilder) { |
||||
|
} |
||||
|
|
||||
|
registerOnChange(fn: any): void { |
||||
|
this.propagateChange = fn; |
||||
|
} |
||||
|
|
||||
|
registerOnTouched(fn: any): void { |
||||
|
} |
||||
|
|
||||
|
ngOnInit() { |
||||
|
this.mobileActionFormGroup = this.fb.group({ |
||||
|
type: [null, Validators.required], |
||||
|
handleEmptyResultFunction: [null], |
||||
|
handleErrorFunction: [null] |
||||
|
}); |
||||
|
this.mobileActionFormGroup.get('type').valueChanges.subscribe((type: WidgetMobileActionType) => { |
||||
|
let action: WidgetMobileActionDescriptor = this.mobileActionFormGroup.value; |
||||
|
if (this.mobileActionTypeFormGroup) { |
||||
|
action = {...action, ...this.mobileActionTypeFormGroup.value}; |
||||
|
} |
||||
|
this.updateMobileActionType(type, action); |
||||
|
}); |
||||
|
this.mobileActionFormGroup.valueChanges.subscribe(() => { |
||||
|
this.updateModel(); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
setDisabledState(isDisabled: boolean): void { |
||||
|
this.disabled = isDisabled; |
||||
|
if (this.disabled) { |
||||
|
this.mobileActionFormGroup.disable({emitEvent: false}); |
||||
|
if (this.mobileActionTypeFormGroup) { |
||||
|
this.mobileActionTypeFormGroup.disable({emitEvent: false}); |
||||
|
} |
||||
|
} else { |
||||
|
this.mobileActionFormGroup.enable({emitEvent: false}); |
||||
|
if (this.mobileActionTypeFormGroup) { |
||||
|
this.mobileActionTypeFormGroup.enable({emitEvent: false}); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
writeValue(value: WidgetMobileActionDescriptor | null): void { |
||||
|
this.mobileActionFormGroup.patchValue({type: value?.type, |
||||
|
handleEmptyResultFunction: value?.handleEmptyResultFunction, |
||||
|
handleErrorFunction: value?.handleErrorFunction}, {emitEvent: false}); |
||||
|
this.updateMobileActionType(value?.type, value); |
||||
|
} |
||||
|
|
||||
|
private updateModel() { |
||||
|
let descriptor: WidgetMobileActionDescriptor = null; |
||||
|
if (this.mobileActionFormGroup.valid && this.mobileActionTypeFormGroup.valid) { |
||||
|
descriptor = { ...this.mobileActionFormGroup.getRawValue(), ...this.mobileActionTypeFormGroup.getRawValue() }; |
||||
|
} |
||||
|
this.propagateChange(descriptor); |
||||
|
} |
||||
|
|
||||
|
private updateMobileActionType(type?: WidgetMobileActionType, action?: WidgetMobileActionDescriptor) { |
||||
|
const prevType = action?.type; |
||||
|
const targetType = type || prevType; |
||||
|
const changed = prevType !== type; |
||||
|
if (changed && targetType) { |
||||
|
let handleEmptyResultFunction = action?.handleEmptyResultFunction; |
||||
|
const defaultHandleEmptyResultFunction = getDefaultHandleEmptyResultFunction(targetType); |
||||
|
if (defaultHandleEmptyResultFunction !== handleEmptyResultFunction) { |
||||
|
handleEmptyResultFunction = getDefaultHandleEmptyResultFunction(type); |
||||
|
this.mobileActionFormGroup.patchValue({handleEmptyResultFunction}, {emitEvent: false}); |
||||
|
} |
||||
|
let handleErrorFunction = action?.handleErrorFunction; |
||||
|
const defaultHandleErrorFunction = getDefaultHandleErrorFunction(targetType); |
||||
|
if (defaultHandleErrorFunction !== handleErrorFunction) { |
||||
|
handleErrorFunction = getDefaultHandleErrorFunction(type); |
||||
|
this.mobileActionFormGroup.patchValue({handleErrorFunction}, {emitEvent: false}); |
||||
|
} |
||||
|
} |
||||
|
this.mobileActionTypeFormGroup = this.fb.group({}); |
||||
|
if (type) { |
||||
|
switch (type) { |
||||
|
case WidgetMobileActionType.takePictureFromGallery: |
||||
|
case WidgetMobileActionType.takePhoto: |
||||
|
case WidgetMobileActionType.takeScreenshot: |
||||
|
let processImageFunction = action?.processImageFunction; |
||||
|
if (changed) { |
||||
|
const defaultProcessImageFunction = getDefaultProcessImageFunction(targetType); |
||||
|
if (defaultProcessImageFunction !== processImageFunction) { |
||||
|
processImageFunction = getDefaultProcessImageFunction(type); |
||||
|
} |
||||
|
} |
||||
|
this.mobileActionTypeFormGroup.addControl( |
||||
|
'processImageFunction', |
||||
|
this.fb.control(processImageFunction, []) |
||||
|
); |
||||
|
break; |
||||
|
case WidgetMobileActionType.mapDirection: |
||||
|
case WidgetMobileActionType.mapLocation: |
||||
|
let getLocationFunction = action?.getLocationFunction; |
||||
|
let processLaunchResultFunction = action?.processLaunchResultFunction; |
||||
|
if (changed) { |
||||
|
const defaultGetLocationFunction = getDefaultGetLocationFunction(); |
||||
|
if (defaultGetLocationFunction !== getLocationFunction) { |
||||
|
getLocationFunction = defaultGetLocationFunction; |
||||
|
} |
||||
|
const defaultProcessLaunchResultFunction = getDefaultProcessLaunchResultFunction(targetType); |
||||
|
if (defaultProcessLaunchResultFunction !== processLaunchResultFunction) { |
||||
|
processLaunchResultFunction = getDefaultProcessLaunchResultFunction(type); |
||||
|
} |
||||
|
} |
||||
|
this.mobileActionTypeFormGroup.addControl( |
||||
|
'getLocationFunction', |
||||
|
this.fb.control(getLocationFunction, [Validators.required]) |
||||
|
); |
||||
|
this.mobileActionTypeFormGroup.addControl( |
||||
|
'processLaunchResultFunction', |
||||
|
this.fb.control(processLaunchResultFunction, []) |
||||
|
); |
||||
|
break; |
||||
|
case WidgetMobileActionType.scanQrCode: |
||||
|
let processQrCodeFunction = action?.processQrCodeFunction; |
||||
|
if (changed) { |
||||
|
const defaultProcessQrCodeFunction = getDefaultProcessQrCodeFunction(); |
||||
|
if (defaultProcessQrCodeFunction !== processQrCodeFunction) { |
||||
|
processQrCodeFunction = defaultProcessQrCodeFunction; |
||||
|
} |
||||
|
} |
||||
|
this.mobileActionTypeFormGroup.addControl( |
||||
|
'processQrCodeFunction', |
||||
|
this.fb.control(processQrCodeFunction, []) |
||||
|
); |
||||
|
break; |
||||
|
case WidgetMobileActionType.makePhoneCall: |
||||
|
let getPhoneNumberFunction = action?.getPhoneNumberFunction; |
||||
|
processLaunchResultFunction = action?.processLaunchResultFunction; |
||||
|
if (changed) { |
||||
|
const defaultGetPhoneNumberFunction = getDefaultGetPhoneNumberFunction(); |
||||
|
if (defaultGetPhoneNumberFunction !== getPhoneNumberFunction) { |
||||
|
getPhoneNumberFunction = defaultGetPhoneNumberFunction; |
||||
|
} |
||||
|
const defaultProcessLaunchResultFunction = getDefaultProcessLaunchResultFunction(targetType); |
||||
|
if (defaultProcessLaunchResultFunction !== processLaunchResultFunction) { |
||||
|
processLaunchResultFunction = getDefaultProcessLaunchResultFunction(type); |
||||
|
} |
||||
|
} |
||||
|
this.mobileActionTypeFormGroup.addControl( |
||||
|
'getPhoneNumberFunction', |
||||
|
this.fb.control(getPhoneNumberFunction, [Validators.required]) |
||||
|
); |
||||
|
this.mobileActionTypeFormGroup.addControl( |
||||
|
'processLaunchResultFunction', |
||||
|
this.fb.control(processLaunchResultFunction, []) |
||||
|
); |
||||
|
break; |
||||
|
case WidgetMobileActionType.getLocation: |
||||
|
let processLocationFunction = action?.processLocationFunction; |
||||
|
if (changed) { |
||||
|
const defaultProcessLocationFunction = getDefaultProcessLocationFunction(); |
||||
|
if (defaultProcessLocationFunction !== processLocationFunction) { |
||||
|
processLocationFunction = defaultProcessLocationFunction; |
||||
|
} |
||||
|
} |
||||
|
this.mobileActionTypeFormGroup.addControl( |
||||
|
'processLocationFunction', |
||||
|
this.fb.control(processLocationFunction, [Validators.required]) |
||||
|
); |
||||
|
break; |
||||
|
} |
||||
|
} |
||||
|
this.mobileActionTypeFormGroup.valueChanges.subscribe(() => { |
||||
|
this.updateModel(); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
public validateOnSubmit() { |
||||
|
for (const jsFuncComponent of this.jsFuncComponents.toArray()) { |
||||
|
jsFuncComponent.validateOnSubmit(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,317 @@ |
|||||
|
///
|
||||
|
/// Copyright © 2016-2021 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 { WidgetMobileActionType } from '@shared/models/widget.models'; |
||||
|
|
||||
|
const processImageFunctionTemplate = |
||||
|
'// Function body to process image obtained as a result of mobile action (take photo, take image from gallery, etc.). \n' + |
||||
|
'// - imageUrl - image URL in base64 data format\n\n' + |
||||
|
'showImageDialog(\'--TITLE--\', imageUrl);\n' + |
||||
|
'//saveEntityImageAttribute(\'image\', imageUrl);\n' + |
||||
|
'\n' + |
||||
|
'function showImageDialog(title, imageUrl) {\n' + |
||||
|
' setTimeout(function() {\n' + |
||||
|
' widgetContext.customDialog.customDialog(imageDialogTemplate, ImageDialogController, {imageUrl: imageUrl, title: title}).subscribe();\n' + |
||||
|
' }, 100);\n' + |
||||
|
'}\n' + |
||||
|
'\n' + |
||||
|
'function saveEntityImageAttribute(attributeName, imageUrl) {\n' + |
||||
|
' if (entityId) {\n' + |
||||
|
' let attributes = [{\n' + |
||||
|
' key: attributeName, value: imageUrl\n' + |
||||
|
' }];\n' + |
||||
|
' widgetContext.attributeService.saveEntityAttributes(entityId, "SERVER_SCOPE", attributes).subscribe(\n' + |
||||
|
' function() {\n' + |
||||
|
' widgetContext.showSuccessToast(\'Image attribute saved!\');\n' + |
||||
|
' },\n' + |
||||
|
' function(error) {\n' + |
||||
|
' widgetContext.dialogs.alert(\'Image attribute save failed\', JSON.stringify(error));\n' + |
||||
|
' }\n' + |
||||
|
' );\n' + |
||||
|
' }\n' + |
||||
|
'}\n' + |
||||
|
'\n' + |
||||
|
'var\n' + |
||||
|
' imageDialogTemplate =\n' + |
||||
|
' \'<div aria-label="Image">\' +\n' + |
||||
|
' \'<form #theForm="ngForm">\' +\n' + |
||||
|
' \'<mat-toolbar fxLayout="row" color="primary">\' +\n' + |
||||
|
' \'<h2>{{title}}</h2>\' +\n' + |
||||
|
' \'<span fxFlex></span>\' +\n' + |
||||
|
' \'<button mat-icon-button (click)="close()">\' +\n' + |
||||
|
' \'<mat-icon>close</mat-icon>\' +\n' + |
||||
|
' \'</button>\' +\n' + |
||||
|
' \'</mat-toolbar>\' +\n' + |
||||
|
' \'<div mat-dialog-content>\' +\n' + |
||||
|
' \'<div class="mat-content mat-padding">\' +\n' + |
||||
|
' \'<div fxLayout="column" fxFlex>\' +\n' + |
||||
|
' \'<div style="padding-top: 20px;">\' +\n' + |
||||
|
' \'<img [src]="imageUrl" style="height: 300px;"/>\' +\n' + |
||||
|
' \'</div>\' +\n' + |
||||
|
' \'</div>\' +\n' + |
||||
|
' \'</div>\' +\n' + |
||||
|
' \'</div>\' +\n' + |
||||
|
' \'<div mat-dialog-actions fxLayout="row">\' +\n' + |
||||
|
' \'<span fxFlex></span>\' +\n' + |
||||
|
' \'<button mat-button (click)="close()" style="margin-right:20px;">Close</button>\' +\n' + |
||||
|
' \'</div>\' +\n' + |
||||
|
' \'</form>\' +\n' + |
||||
|
' \'</div>\';\n' + |
||||
|
'\n' + |
||||
|
'function ImageDialogController(instance) {\n' + |
||||
|
' let vm = instance;\n' + |
||||
|
' vm.title = vm.data.title;\n' + |
||||
|
' vm.imageUrl = vm.data.imageUrl;\n' + |
||||
|
' vm.close = function ()\n' + |
||||
|
' {\n' + |
||||
|
' vm.dialogRef.close(null);\n' + |
||||
|
' }\n' + |
||||
|
'}\n'; |
||||
|
|
||||
|
const processLaunchResultFunctionTemplate = |
||||
|
'// Optional function body to process result of attempt to launch external mobile application (for ex. map application or phone call application). \n' + |
||||
|
'// - launched - boolean value indicating if the external application was successfully launched.\n\n' + |
||||
|
'showLaunchStatusDialog(\'--TITLE--\', launched);\n' + |
||||
|
'\n' + |
||||
|
'function showLaunchStatusDialog(title, status) {\n' + |
||||
|
' setTimeout(function() {\n' + |
||||
|
' widgetContext.dialogs.alert(title, status ? \'Successfully launched\' : \'Failed to launch\').subscribe();\n' + |
||||
|
' }, 100);\n' + |
||||
|
'}\n'; |
||||
|
|
||||
|
const processQrCodeFunction = |
||||
|
'// Function body to process result of QR code scanning. \n' + |
||||
|
'// - code - scanned QR code\n' + |
||||
|
'// - format - scanned QR code format\n\n' + |
||||
|
'showQrCodeDialog(\'QR Code\', code, format);\n' + |
||||
|
'\n' + |
||||
|
'function showQrCodeDialog(title, code, format) {\n' + |
||||
|
' setTimeout(function() {\n' + |
||||
|
' widgetContext.dialogs.alert(title, \'Code: [\'+code+\']<br>Format: \' + format).subscribe();\n' + |
||||
|
' }, 100);\n' + |
||||
|
'}\n'; |
||||
|
|
||||
|
const processLocationFunction = |
||||
|
'// Function body to process current location of the phone. \n' + |
||||
|
'// - latitude - phone location latitude\n' + |
||||
|
'// - longitude - phone location longitude\n\n' + |
||||
|
'showLocationDialog(\'Location\', latitude, longitude);\n' + |
||||
|
'// saveEntityLocationAttributes(\'latitude\', \'longitude\', latitude, longitude);\n' + |
||||
|
'\n' + |
||||
|
'function showImageDialog(title, imageUrl) {\n' + |
||||
|
' setTimeout(function() {\n' + |
||||
|
' widgetContext.customDialog.customDialog(imageDialogTemplate, ImageDialogController, {imageUrl: imageUrl, title: title}).subscribe();\n' + |
||||
|
' }, 100);\n' + |
||||
|
'}\n' + |
||||
|
'\n' + |
||||
|
'function saveEntityLocationAttributes(latitudeAttributeName, longitudeAttributeName, latitude, longitude) {\n' + |
||||
|
' if (entityId) {\n' + |
||||
|
' let attributes = [\n' + |
||||
|
' { key: latitudeAttributeName, value: latitude },\n' + |
||||
|
' { key: longitudeAttributeName, value: longitude }\n' + |
||||
|
' ];\n' + |
||||
|
' widgetContext.attributeService.saveEntityAttributes(entityId, "SERVER_SCOPE", attributes).subscribe(\n' + |
||||
|
' function() {\n' + |
||||
|
' widgetContext.showSuccessToast(\'Location attributes saved!\');\n' + |
||||
|
' },\n' + |
||||
|
' function(error) {\n' + |
||||
|
' widgetContext.dialogs.alert(\'Location attributes save failed\', JSON.stringify(error));\n' + |
||||
|
' }\n' + |
||||
|
' );\n' + |
||||
|
' }\n' + |
||||
|
'}\n' + |
||||
|
'\n' + |
||||
|
'\n' + |
||||
|
'function showLocationDialog(title, latitude, longitude) {\n' + |
||||
|
' setTimeout(function() {\n' + |
||||
|
' widgetContext.dialogs.alert(title, \'Latitude: \'+latitude+\'<br>Longitude: \' + longitude).subscribe();\n' + |
||||
|
' }, 100);\n' + |
||||
|
'}'; |
||||
|
|
||||
|
const handleEmptyResultFunctionTemplate = |
||||
|
'// Optional function body to handle empty result. \n' + |
||||
|
'// Usually this happens when user cancels the action (for ex. by pressing phone back button). \n\n' + |
||||
|
'showEmptyResultDialog(\'--MESSAGE--\');\n' + |
||||
|
'\n' + |
||||
|
'function showEmptyResultDialog(message) {\n' + |
||||
|
' setTimeout(function() {\n' + |
||||
|
' widgetContext.dialogs.alert(\'Empty result\', message).subscribe();\n' + |
||||
|
' }, 100);\n' + |
||||
|
'}\n'; |
||||
|
|
||||
|
const handleErrorFunctionTemplate = |
||||
|
'// Optional function body to handle error occurred while mobile action execution \n' + |
||||
|
'// - error - Error message\n\n' + |
||||
|
'showErrorDialog(\'--TITLE--\', error);\n' + |
||||
|
'\n' + |
||||
|
'function showErrorDialog(title, error) {\n' + |
||||
|
' setTimeout(function() {\n' + |
||||
|
' widgetContext.dialogs.alert(title, error).subscribe();\n' + |
||||
|
' }, 100);\n' + |
||||
|
'}\n'; |
||||
|
|
||||
|
const getLocationFunctionTemplate = |
||||
|
'// Function body that should return location as array of two numbers (latitude, longitude) for further processing by mobile action.\n' + |
||||
|
'// Usually location can be obtained from entity attributes/telemetry. \n\n' + |
||||
|
'return getLocationFromEntityAttributes();\n' + |
||||
|
'//return [30, 30]; // TEST LOCATION\n' + |
||||
|
'\n' + |
||||
|
'\n' + |
||||
|
'function getLocationFromEntityAttributes() {\n' + |
||||
|
' if (entityId) {\n' + |
||||
|
' return widgetContext.attributeService.getEntityAttributes(entityId, \'SERVER_SCOPE\', [\'latitude\', \'longitude\']).pipe(widgetContext.rxjs.map(function(attributeData) {\n' + |
||||
|
' var res = [0,0];\n' + |
||||
|
' if (attributeData && attributeData.length === 2) {\n' + |
||||
|
' res[0] = attributeData.filter(function (data) { return data.key === \'latitude\'})[0].value;\n' + |
||||
|
' res[1] = attributeData.filter(function (data) { return data.key === \'longitude\'})[0].value;\n' + |
||||
|
' }\n' + |
||||
|
' return res;\n' + |
||||
|
' }));\n' + |
||||
|
' } else {\n' + |
||||
|
' return [0,0];\n' + |
||||
|
' }\n' + |
||||
|
'}\n'; |
||||
|
|
||||
|
const getPhoneNumberFunctionTemplate = |
||||
|
'// Function body that should return phone number for further processing by mobile action.\n' + |
||||
|
'// Usually phone number can be obtained from entity attributes/telemetry. \n\n' + |
||||
|
'return getPhoneNumberFromEntityAttributes();\n' + |
||||
|
'//return 123456789; // TEST PHONE NUMBER\n' + |
||||
|
'\n' + |
||||
|
'\n' + |
||||
|
'function getPhoneNumberFromEntityAttributes() {\n' + |
||||
|
' if (entityId) {\n' + |
||||
|
' return widgetContext.attributeService.getEntityAttributes(entityId, \'SERVER_SCOPE\', [\'phone\']).pipe(widgetContext.rxjs.map(function(attributeData) {\n' + |
||||
|
' var res = 0;\n' + |
||||
|
' if (attributeData && attributeData.length === 1) {\n' + |
||||
|
' res = attributeData[0].value;\n' + |
||||
|
' }\n' + |
||||
|
' return res;\n' + |
||||
|
' }));\n' + |
||||
|
' } else {\n' + |
||||
|
' return 0;\n' + |
||||
|
' }\n' + |
||||
|
'}\n'; |
||||
|
|
||||
|
export function getDefaultProcessImageFunction(type: WidgetMobileActionType): string { |
||||
|
let title; |
||||
|
switch (type) { |
||||
|
case WidgetMobileActionType.takePictureFromGallery: |
||||
|
title = 'Gallery picture'; |
||||
|
break; |
||||
|
case WidgetMobileActionType.takePhoto: |
||||
|
title = 'Photo'; |
||||
|
break; |
||||
|
case WidgetMobileActionType.takeScreenshot: |
||||
|
title = 'Screenshot'; |
||||
|
break; |
||||
|
} |
||||
|
return processImageFunctionTemplate.replace('--TITLE--', title); |
||||
|
} |
||||
|
|
||||
|
export function getDefaultProcessLaunchResultFunction(type: WidgetMobileActionType): string { |
||||
|
let title; |
||||
|
switch (type) { |
||||
|
case WidgetMobileActionType.mapLocation: |
||||
|
title = 'Map location'; |
||||
|
break; |
||||
|
case WidgetMobileActionType.mapDirection: |
||||
|
title = 'Map direction'; |
||||
|
break; |
||||
|
case WidgetMobileActionType.makePhoneCall: |
||||
|
title = 'Phone call'; |
||||
|
break; |
||||
|
} |
||||
|
return processLaunchResultFunctionTemplate.replace('--TITLE--', title); |
||||
|
} |
||||
|
|
||||
|
export function getDefaultProcessQrCodeFunction() { |
||||
|
return processQrCodeFunction; |
||||
|
} |
||||
|
|
||||
|
export function getDefaultProcessLocationFunction() { |
||||
|
return processLocationFunction; |
||||
|
} |
||||
|
|
||||
|
export function getDefaultGetLocationFunction() { |
||||
|
return getLocationFunctionTemplate; |
||||
|
} |
||||
|
|
||||
|
export function getDefaultGetPhoneNumberFunction() { |
||||
|
return getPhoneNumberFunctionTemplate; |
||||
|
} |
||||
|
|
||||
|
export function getDefaultHandleEmptyResultFunction(type: WidgetMobileActionType): string { |
||||
|
let message = 'Mobile action was cancelled!'; |
||||
|
switch (type) { |
||||
|
case WidgetMobileActionType.takePictureFromGallery: |
||||
|
message = 'Take picture from gallery action was cancelled!'; |
||||
|
break; |
||||
|
case WidgetMobileActionType.takePhoto: |
||||
|
message = 'Take photo action was cancelled!'; |
||||
|
break; |
||||
|
case WidgetMobileActionType.mapDirection: |
||||
|
message = 'Open map directions was not invoked!'; |
||||
|
break; |
||||
|
case WidgetMobileActionType.mapLocation: |
||||
|
message = 'Open location on map was not invoked!'; |
||||
|
break; |
||||
|
case WidgetMobileActionType.scanQrCode: |
||||
|
message = 'Scan QR code action was canceled!'; |
||||
|
break; |
||||
|
case WidgetMobileActionType.makePhoneCall: |
||||
|
message = 'Phone call was not invoked!'; |
||||
|
break; |
||||
|
case WidgetMobileActionType.getLocation: |
||||
|
message = 'Get location action was canceled!'; |
||||
|
break; |
||||
|
case WidgetMobileActionType.takeScreenshot: |
||||
|
message = 'Take screenshot action was cancelled!'; |
||||
|
break; |
||||
|
} |
||||
|
return handleEmptyResultFunctionTemplate.replace('--MESSAGE--', message); |
||||
|
} |
||||
|
|
||||
|
export function getDefaultHandleErrorFunction(type: WidgetMobileActionType): string { |
||||
|
let title = 'Mobile action failed'; |
||||
|
switch (type) { |
||||
|
case WidgetMobileActionType.takePictureFromGallery: |
||||
|
title = 'Failed to take picture from gallery'; |
||||
|
break; |
||||
|
case WidgetMobileActionType.takePhoto: |
||||
|
title = 'Failed to take photo'; |
||||
|
break; |
||||
|
case WidgetMobileActionType.mapDirection: |
||||
|
title = 'Failed to open map directions'; |
||||
|
break; |
||||
|
case WidgetMobileActionType.mapLocation: |
||||
|
title = 'Failed to open map location'; |
||||
|
break; |
||||
|
case WidgetMobileActionType.scanQrCode: |
||||
|
title = 'Failed to scan QR code'; |
||||
|
break; |
||||
|
case WidgetMobileActionType.makePhoneCall: |
||||
|
title = 'Failed to make phone call'; |
||||
|
break; |
||||
|
case WidgetMobileActionType.getLocation: |
||||
|
title = 'Failed to get phone location'; |
||||
|
break; |
||||
|
case WidgetMobileActionType.takeScreenshot: |
||||
|
title = 'Failed to take screenshot'; |
||||
|
break; |
||||
|
} |
||||
|
return handleErrorFunctionTemplate.replace('--TITLE--', title); |
||||
|
} |
||||
Loading…
Reference in new issue