UI: Optimizations

This commit is contained in:
Igor Kulikov 2020-12-28 16:06:36 +02:00
parent 0ae74b77e3
commit 1ff3eeaf93
33 changed files with 1293 additions and 798 deletions

View File

@ -27,22 +27,22 @@
"src/assets",
{
"glob": "worker-html.js",
"input": "./node_modules/ace-builds/src-min/",
"input": "./node_modules/ace-builds/src-noconflict/",
"output": "/"
},
{
"glob": "worker-css.js",
"input": "./node_modules/ace-builds/src-min/",
"input": "./node_modules/ace-builds/src-noconflict/",
"output": "/"
},
{
"glob": "worker-json.js",
"input": "./node_modules/ace-builds/src-min/",
"input": "./node_modules/ace-builds/src-noconflict/",
"output": "/"
},
{
"glob": "worker-javascript.js",
"input": "./node_modules/ace-builds/src-min/",
"input": "./node_modules/ace-builds/src-noconflict/",
"output": "/"
},
{
@ -102,24 +102,6 @@
"node_modules/js-beautify/js/lib/beautify.js",
"node_modules/js-beautify/js/lib/beautify-css.js",
"node_modules/js-beautify/js/lib/beautify-html.js",
"node_modules/ace-builds/src-min/ace.js",
"node_modules/ace-builds/src-min/ext-language_tools.js",
"node_modules/ace-builds/src-min/ext-searchbox.js",
"node_modules/ace-builds/src-min/theme-github.js",
"node_modules/ace-builds/src-min/mode-text.js",
"node_modules/ace-builds/src-min/mode-markdown.js",
"node_modules/ace-builds/src-min/mode-html.js",
"node_modules/ace-builds/src-min/mode-css.js",
"node_modules/ace-builds/src-min/mode-json.js",
"node_modules/ace-builds/src-min/mode-java.js",
"node_modules/ace-builds/src-min/mode-javascript.js",
"node_modules/ace-builds/src-min/snippets/text.js",
"node_modules/ace-builds/src-min/snippets/markdown.js",
"node_modules/ace-builds/src-min/snippets/html.js",
"node_modules/ace-builds/src-min/snippets/css.js",
"node_modules/ace-builds/src-min/snippets/json.js",
"node_modules/ace-builds/src-min/snippets/java.js",
"node_modules/ace-builds/src-min/snippets/javascript.js",
"node_modules/systemjs/dist/system.js",
"node_modules/jstree/dist/jstree.min.js"
],
@ -143,7 +125,11 @@
"hoist-non-react-statics",
"classnames",
"raf",
"moment-timezone"
"moment-timezone",
"tinycolor2",
"json-schema-defaults",
"leaflet-providers",
"lodash"
]
},
"configurations": {

View File

@ -69,16 +69,19 @@ export function addCondition(schema: JsonSettingsSchema, condition: string, excl
return {
key: element,
condition
}
};
}
if (typeof element === 'object') {
if (element.condition) {
element.condition += ' && ' + condition
element.condition += ' && ' + condition;
}
else {
element.condition = condition;
}
else element.condition = condition;
}
}
return element;
});
return schema;
}

View File

@ -19,10 +19,10 @@ import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state';
import { ActionStatus, AuditLog } from '@shared/models/audit-log.models';
import * as ace from 'ace-builds';
import { Ace } from 'ace-builds';
import { DialogComponent } from '@shared/components/dialog.component';
import { Router } from '@angular/router';
import { getAce } from '@shared/models/ace/ace.models';
export interface AuditLogDetailsDialogData {
auditLog: AuditLog;
@ -37,11 +37,9 @@ export class AuditLogDetailsDialogComponent extends DialogComponent<AuditLogDeta
@ViewChild('actionDataEditor', {static: true})
actionDataEditorElmRef: ElementRef;
private actionDataEditor: ace.Ace.Editor;
@ViewChild('failureDetailsEditor', {static: true})
failureDetailsEditorElmRef: ElementRef;
private failureDetailsEditor: ace.Ace.Editor;
auditLog: AuditLog;
displayFailureDetails: boolean;
@ -62,15 +60,15 @@ export class AuditLogDetailsDialogComponent extends DialogComponent<AuditLogDeta
this.actionData = this.auditLog.actionData ? JSON.stringify(this.auditLog.actionData, null, 2) : '';
this.actionFailureDetails = this.auditLog.actionFailureDetails;
this.actionDataEditor = this.createEditor(this.actionDataEditorElmRef, this.actionData);
this.createEditor(this.actionDataEditorElmRef, this.actionData);
if (this.displayFailureDetails) {
this.failureDetailsEditor = this.createEditor(this.failureDetailsEditorElmRef, this.actionFailureDetails);
this.createEditor(this.failureDetailsEditorElmRef, this.actionFailureDetails);
}
}
createEditor(editorElementRef: ElementRef, content: string): ace.Ace.Editor {
createEditor(editorElementRef: ElementRef, content: string): void {
const editorElement = editorElementRef.nativeElement;
let editorOptions: Partial<ace.Ace.EditorOptions> = {
let editorOptions: Partial<Ace.EditorOptions> = {
mode: 'ace/mode/java',
theme: 'ace/theme/github',
showGutter: false,
@ -85,14 +83,17 @@ export class AuditLogDetailsDialogComponent extends DialogComponent<AuditLogDeta
};
editorOptions = {...editorOptions, ...advancedOptions};
const editor = ace.edit(editorElement, editorOptions);
editor.session.setUseWrapMode(false);
editor.setValue(content, -1);
this.updateEditorSize(editorElement, content, editor);
return editor;
getAce().subscribe(
(ace) => {
const editor = ace.edit(editorElement, editorOptions);
editor.session.setUseWrapMode(false);
editor.setValue(content, -1);
this.updateEditorSize(editorElement, content, editor);
}
);
}
updateEditorSize(editorElement: any, content: string, editor: ace.Ace.Editor) {
updateEditorSize(editorElement: any, content: string, editor: Ace.Editor) {
let newHeight = 200;
let newWidth = 600;
if (content && content.length > 0) {

View File

@ -19,10 +19,11 @@ import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state';
import * as ace from 'ace-builds';
import { Ace } from 'ace-builds';
import { DialogComponent } from '@shared/components/dialog.component';
import { Router } from '@angular/router';
import { ContentType, contentTypesMap } from '@shared/models/constants';
import { getAce } from '@shared/models/ace/ace.models';
export interface EventContentDialogData {
content: string;
@ -39,7 +40,6 @@ export class EventContentDialogComponent extends DialogComponent<EventContentDia
@ViewChild('eventContentEditor', {static: true})
eventContentEditorElmRef: ElementRef;
private eventContentEditor: ace.Ace.Editor;
content: string;
title: string;
@ -58,10 +58,10 @@ export class EventContentDialogComponent extends DialogComponent<EventContentDia
this.title = this.data.title;
this.contentType = this.data.contentType;
this.eventContentEditor = this.createEditor(this.eventContentEditorElmRef, this.content);
this.createEditor(this.eventContentEditorElmRef, this.content);
}
createEditor(editorElementRef: ElementRef, content: string): ace.Ace.Editor {
createEditor(editorElementRef: ElementRef, content: string) {
const editorElement = editorElementRef.nativeElement;
let mode = 'java';
if (this.contentType) {
@ -70,7 +70,7 @@ export class EventContentDialogComponent extends DialogComponent<EventContentDia
content = js_beautify(content, {indent_size: 4});
}
}
let editorOptions: Partial<ace.Ace.EditorOptions> = {
let editorOptions: Partial<Ace.EditorOptions> = {
mode: `ace/mode/${mode}`,
theme: 'ace/theme/github',
showGutter: false,
@ -85,14 +85,17 @@ export class EventContentDialogComponent extends DialogComponent<EventContentDia
};
editorOptions = {...editorOptions, ...advancedOptions};
const editor = ace.edit(editorElement, editorOptions);
editor.session.setUseWrapMode(false);
editor.setValue(content, -1);
this.updateEditorSize(editorElement, content, editor);
return editor;
getAce().subscribe(
(ace) => {
const editor = ace.edit(editorElement, editorOptions);
editor.session.setUseWrapMode(false);
editor.setValue(content, -1);
this.updateEditorSize(editorElement, content, editor);
}
);
}
updateEditorSize(editorElement: any, content: string, editor: ace.Ace.Editor) {
updateEditorSize(editorElement: any, content: string, editor: Ace.Editor) {
let newHeight = 400;
let newWidth = 600;
if (content && content.length > 0) {

View File

@ -220,7 +220,7 @@ export class KeyFilterDialogComponent extends
let keyNameObservable: Observable<Array<string>>;
switch (this.keyFilterFormGroup.get('key.type').value) {
case EntityKeyType.ENTITY_FIELD:
keyNameObservable = of(Object.values(entityFields).map(entityField => entityField.keyName).sort());
keyNameObservable = of(Object.keys(entityFields).map(itm => entityFields[itm]).map(entityField => entityField.keyName).sort());
break;
case EntityKeyType.ATTRIBUTE:
keyNameObservable = this.deviceProfileService.getDeviceProfileDevicesAttributesKeys(

View File

@ -32,11 +32,15 @@ import { PageComponent } from '@shared/components/page.component';
import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state';
import { CustomActionDescriptor } from '@shared/models/widget.models';
import * as ace from 'ace-builds';
import { Ace } from 'ace-builds';
import { CancelAnimationFrame, RafService } from '@core/services/raf.service';
import { css_beautify, html_beautify } from 'js-beautify';
import { ResizeObserver } from '@juggle/resize-observer';
import { CustomPrettyActionEditorCompleter } from '@home/components/widget/action/custom-action.models';
import { Observable } from 'rxjs/internal/Observable';
import { forkJoin, from } from 'rxjs';
import { map, tap } from 'rxjs/operators';
import { getAce } from '@shared/models/ace/ace.models';
@Component({
selector: 'tb-custom-action-pretty-resources-tabs',
@ -64,11 +68,11 @@ export class CustomActionPrettyResourcesTabsComponent extends PageComponent impl
htmlFullscreen = false;
cssFullscreen = false;
aceEditors: ace.Ace.Editor[] = [];
aceEditors: Ace.Editor[] = [];
editorsResizeCafs: {[editorId: string]: CancelAnimationFrame} = {};
aceResize$: ResizeObserver;
htmlEditor: ace.Ace.Editor;
cssEditor: ace.Ace.Editor;
htmlEditor: Ace.Editor;
cssEditor: Ace.Editor;
setValuesPending = false;
customPrettyActionEditorCompleter = CustomPrettyActionEditorCompleter;
@ -80,11 +84,12 @@ export class CustomActionPrettyResourcesTabsComponent extends PageComponent impl
}
ngOnInit(): void {
this.initAceEditors();
if (this.setValuesPending) {
this.setAceEditorValues();
this.setValuesPending = false;
}
this.initAceEditors().subscribe(() => {
if (this.setValuesPending) {
this.setAceEditorValues();
this.setValuesPending = false;
}
});
}
ngOnDestroy(): void {
@ -94,7 +99,7 @@ export class CustomActionPrettyResourcesTabsComponent extends PageComponent impl
ngOnChanges(changes: SimpleChanges): void {
for (const propName of Object.keys(changes)) {
if (propName === 'action') {
if (this.aceEditors.length) {
if (this.aceEditors.length === 2) {
this.setAceEditorValues();
} else {
this.setValuesPending = true;
@ -152,34 +157,46 @@ export class CustomActionPrettyResourcesTabsComponent extends PageComponent impl
}
}
private initAceEditors() {
private initAceEditors(): Observable<any> {
this.aceResize$ = new ResizeObserver((entries) => {
entries.forEach((entry) => {
const editor = this.aceEditors.find(aceEditor => aceEditor.container === entry.target);
this.onAceEditorResize(editor);
});
});
this.htmlEditor = this.createAceEditor(this.htmlInputElmRef, 'html');
this.htmlEditor.on('input', () => {
const editorValue = this.htmlEditor.getValue();
if (this.action.customHtml !== editorValue) {
this.action.customHtml = editorValue;
this.notifyActionUpdated();
}
});
this.cssEditor = this.createAceEditor(this.cssInputElmRef, 'css');
this.cssEditor.on('input', () => {
const editorValue = this.cssEditor.getValue();
if (this.action.customCss !== editorValue) {
this.action.customCss = editorValue;
this.notifyActionUpdated();
}
});
const editorsObservables: Observable<any>[] = [];
editorsObservables.push(this.createAceEditor(this.htmlInputElmRef, 'html').pipe(
tap((editor) => {
this.htmlEditor = editor;
this.htmlEditor.on('input', () => {
const editorValue = this.htmlEditor.getValue();
if (this.action.customHtml !== editorValue) {
this.action.customHtml = editorValue;
this.notifyActionUpdated();
}
});
})
));
editorsObservables.push(this.createAceEditor(this.cssInputElmRef, 'css').pipe(
tap((editor) => {
this.cssEditor = editor;
this.cssEditor.on('input', () => {
const editorValue = this.cssEditor.getValue();
if (this.action.customCss !== editorValue) {
this.action.customCss = editorValue;
this.notifyActionUpdated();
}
});
})
));
return forkJoin(editorsObservables);
}
private createAceEditor(editorElementRef: ElementRef, mode: string): ace.Ace.Editor {
private createAceEditor(editorElementRef: ElementRef, mode: string): Observable<Ace.Editor> {
const editorElement = editorElementRef.nativeElement;
let editorOptions: Partial<ace.Ace.EditorOptions> = {
let editorOptions: Partial<Ace.EditorOptions> = {
mode: `ace/mode/${mode}`,
showGutter: true,
showPrintMargin: true
@ -190,11 +207,15 @@ export class CustomActionPrettyResourcesTabsComponent extends PageComponent impl
enableLiveAutocompletion: true
};
editorOptions = {...editorOptions, ...advancedOptions};
const aceEditor = ace.edit(editorElement, editorOptions);
aceEditor.session.setUseWrapMode(true);
this.aceEditors.push(aceEditor);
this.aceResize$.observe(editorElement);
return aceEditor;
return getAce().pipe(
map((ace) => {
const aceEditor = ace.edit(editorElement, editorOptions);
aceEditor.session.setUseWrapMode(true);
this.aceEditors.push(aceEditor);
this.aceResize$.observe(editorElement);
return aceEditor;
})
);
}
private setAceEditorValues() {
@ -202,7 +223,7 @@ export class CustomActionPrettyResourcesTabsComponent extends PageComponent impl
this.cssEditor.setValue(this.action.customCss ? this.action.customCss : '', -1);
}
private onAceEditorResize(aceEditor: ace.Ace.Editor) {
private onAceEditorResize(aceEditor: Ace.Editor) {
if (this.editorsResizeCafs[aceEditor.id]) {
this.editorsResizeCafs[aceEditor.id]();
delete this.editorsResizeCafs[aceEditor.id];

View File

@ -103,7 +103,7 @@ export class GatewayFormComponent extends PageComponent implements OnInit, OnDes
gatewayType: string;
gatewayConfigurationGroup: FormGroup;
securityTypes = SecurityTypeTranslationMap;
gatewayLogLevels = Object.values(GatewayLogLevel);
gatewayLogLevels = Object.keys(GatewayLogLevel).map(itm => GatewayLogLevel[itm]);
connectorTypes = Object.keys(ConnectorType);
storageTypes = StorageTypeTranslationMap;

View File

@ -0,0 +1,445 @@
///
/// Copyright © 2016-2020 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 { FormattedData, MapProviders, ReplaceInfo } from '@home/components/widget/lib/maps/map-models';
import {
createLabelFromDatasource,
hashCode,
isDefined,
isDefinedAndNotNull, isFunction,
isNumber,
isUndefined,
padValue
} from '@core/utils';
import { Observable, Observer, of } from 'rxjs';
import { map } from 'rxjs/operators';
import { Datasource, DatasourceData } from '@shared/models/widget.models';
import _ from 'lodash';
import { mapProviderSchema, providerSets } from '@home/components/widget/lib/maps/schemes';
import { addCondition, mergeSchemes } from '@core/schema-utils';
export function getProviderSchema(mapProvider: MapProviders, ignoreImageMap = false) {
const providerSchema = _.cloneDeep(mapProviderSchema);
if (mapProvider) {
providerSchema.schema.properties.provider.default = mapProvider;
}
if (ignoreImageMap) {
providerSchema.form[0].items = providerSchema.form[0]?.items.filter(item => item.value !== 'image-map');
}
return mergeSchemes([providerSchema,
...Object.keys(providerSets)?.map(
(key: string) => {
const setting = providerSets[key];
return addCondition(setting?.schema, `model.provider === '${setting.name}'`);
})]);
}
export function getRatio(firsMoment: number, secondMoment: number, intermediateMoment: number): number {
return (intermediateMoment - firsMoment) / (secondMoment - firsMoment);
}
export function interpolateOnLineSegment(
pointA: FormattedData,
pointB: FormattedData,
latKeyName: string,
lngKeyName: string,
ratio: number
): { [key: string]: number } {
return {
[latKeyName]: (pointA[latKeyName] + (pointB[latKeyName] - pointA[latKeyName]) * ratio),
[lngKeyName]: (pointA[lngKeyName] + (pointB[lngKeyName] - pointA[lngKeyName]) * ratio)
};
}
export function findAngle(startPoint: FormattedData, endPoint: FormattedData, latKeyName: string, lngKeyName: string): number {
if (isUndefined(startPoint) || isUndefined(endPoint)) {
return 0;
}
let angle = -Math.atan2(endPoint[latKeyName] - startPoint[latKeyName], endPoint[lngKeyName] - startPoint[lngKeyName]);
angle = angle * 180 / Math.PI;
return parseInt(angle.toFixed(2), 10);
}
export function getDefCenterPosition(position) {
if (typeof (position) === 'string') {
return position.split(',');
}
if (typeof (position) === 'object') {
return position;
}
return [0, 0];
}
const imageAspectMap = {};
function imageLoader(imageUrl: string): Observable<HTMLImageElement> {
return new Observable((observer: Observer<HTMLImageElement>) => {
const image = document.createElement('img'); // support IE
image.style.position = 'absolute';
image.style.left = '-99999px';
image.style.top = '-99999px';
image.onload = () => {
observer.next(image);
document.body.removeChild(image);
observer.complete();
};
image.onerror = err => {
observer.error(err);
document.body.removeChild(image);
observer.complete();
};
document.body.appendChild(image);
image.src = imageUrl;
});
}
export function aspectCache(imageUrl: string): Observable<number> {
if (imageUrl?.length) {
const hash = hashCode(imageUrl);
let aspect = imageAspectMap[hash];
if (aspect) {
return of(aspect);
}
return imageLoader(imageUrl).pipe(map(image => {
aspect = image.width / image.height;
imageAspectMap[hash] = aspect;
return aspect;
}));
}
}
export type TranslateFunc = (key: string, defaultTranslation?: string) => string;
const varsRegex = /\${([^}]*)}/g;
const linkActionRegex = /<link-act name=['"]([^['"]*)['"]>([^<]*)<\/link-act>/g;
const buttonActionRegex = /<button-act name=['"]([^['"]*)['"]>([^<]*)<\/button-act>/g;
function createLinkElement(actionName: string, actionText: string): string {
return `<a href="javascript:void(0);" class="tb-custom-action" data-action-name=${actionName}>${actionText}</a>`;
}
function createButtonElement(actionName: string, actionText: string) {
return `<button mat-button class="tb-custom-action" data-action-name=${actionName}>${actionText}</button>`;
}
function parseTemplate(template: string, data: { $datasource?: Datasource, [key: string]: any },
translateFn?: TranslateFunc) {
let res = '';
try {
if (translateFn) {
template = translateFn(template);
}
template = createLabelFromDatasource(data.$datasource, template);
let match = /\${([^}]*)}/g.exec(template);
while (match !== null) {
const variable = match[0];
let label = match[1];
let valDec = 2;
const splitValues = label.split(':');
if (splitValues.length > 1) {
label = splitValues[0];
valDec = parseFloat(splitValues[1]);
}
if (label.startsWith('#')) {
const keyIndexStr = label.substring(1);
const n = Math.floor(Number(keyIndexStr));
if (String(n) === keyIndexStr && n >= 0) {
label = data.$datasource.dataKeys[n].label;
}
}
const value = data[label] || '';
let textValue: string;
if (isNumber(value)) {
textValue = padValue(value, valDec);
} else {
textValue = value;
}
template = template.replace(variable, textValue);
match = /\${([^}]*)}/g.exec(template);
}
let actionTags: string;
let actionText: string;
let actionName: string;
let action: string;
match = linkActionRegex.exec(template);
while (match !== null) {
[actionTags, actionName, actionText] = match;
action = createLinkElement(actionName, actionText);
template = template.replace(actionTags, action);
match = linkActionRegex.exec(template);
}
match = buttonActionRegex.exec(template);
while (match !== null) {
[actionTags, actionName, actionText] = match;
action = createButtonElement(actionName, actionText);
template = template.replace(actionTags, action);
match = buttonActionRegex.exec(template);
}
// const compiled = _.template(template);
// res = compiled(data);
res = template;
} catch (ex) {
console.log(ex, template);
}
return res;
}
export function processPattern(template: string, data: { $datasource?: Datasource, [key: string]: any }): Array<ReplaceInfo> {
const replaceInfo = [];
try {
const reg = /\${([^}]*)}/g;
let match = reg.exec(template);
while (match !== null) {
const variableInfo: ReplaceInfo = {
dataKeyName: '',
valDec: 2,
variable: ''
};
const variable = match[0];
let label = match[1];
let valDec = 2;
const splitValues = label.split(':');
if (splitValues.length > 1) {
label = splitValues[0];
valDec = parseFloat(splitValues[1]);
}
variableInfo.variable = variable;
variableInfo.valDec = valDec;
if (label.startsWith('#')) {
const keyIndexStr = label.substring(1);
const n = Math.floor(Number(keyIndexStr));
if (String(n) === keyIndexStr && n >= 0) {
variableInfo.dataKeyName = data.$datasource.dataKeys[n].label;
}
} else {
variableInfo.dataKeyName = label;
}
replaceInfo.push(variableInfo);
match = reg.exec(template);
}
} catch (ex) {
console.log(ex, template);
}
return replaceInfo;
}
export function fillPattern(markerLabelText: string, replaceInfoLabelMarker: Array<ReplaceInfo>, data: FormattedData) {
let text = createLabelFromDatasource(data.$datasource, markerLabelText);
if (replaceInfoLabelMarker) {
for (const variableInfo of replaceInfoLabelMarker) {
let txtVal = '';
if (variableInfo.dataKeyName && isDefinedAndNotNull(data[variableInfo.dataKeyName])) {
const varData = data[variableInfo.dataKeyName];
if (isNumber(varData)) {
txtVal = padValue(varData, variableInfo.valDec);
} else {
txtVal = varData;
}
}
text = text.replace(variableInfo.variable, txtVal);
}
}
return text;
}
function prepareProcessPattern(template: string, translateFn?: TranslateFunc): string {
if (translateFn) {
template = translateFn(template);
}
let actionTags: string;
let actionText: string;
let actionName: string;
let action: string;
let match = linkActionRegex.exec(template);
while (match !== null) {
[actionTags, actionName, actionText] = match;
action = createLinkElement(actionName, actionText);
template = template.replace(actionTags, action);
match = linkActionRegex.exec(template);
}
match = buttonActionRegex.exec(template);
while (match !== null) {
[actionTags, actionName, actionText] = match;
action = createButtonElement(actionName, actionText);
template = template.replace(actionTags, action);
match = buttonActionRegex.exec(template);
}
return template;
}
export const parseWithTranslation = {
translateFn: null,
translate(key: string, defaultTranslation?: string): string {
if (this.translateFn) {
return this.translateFn(key, defaultTranslation);
} else {
throw console.error('Translate not assigned');
}
},
parseTemplate(template: string, data: object, forceTranslate = false): string {
return parseTemplate(forceTranslate ? this.translate(template) : template, data, this.translate.bind(this));
},
prepareProcessPattern(template: string, forceTranslate = false): string {
return prepareProcessPattern(forceTranslate ? this.translate(template) : template, this.translate.bind(this));
},
setTranslate(translateFn: TranslateFunc) {
this.translateFn = translateFn;
}
};
export function parseData(input: DatasourceData[]): FormattedData[] {
return _(input).groupBy(el => el?.datasource?.entityName)
.values().value().map((entityArray, i) => {
const obj: FormattedData = {
entityName: entityArray[0]?.datasource?.entityName,
entityId: entityArray[0]?.datasource?.entityId,
entityType: entityArray[0]?.datasource?.entityType,
$datasource: entityArray[0]?.datasource,
dsIndex: i,
deviceType: null
};
entityArray.filter(el => el.data.length).forEach(el => {
const indexDate = el?.data?.length ? el.data.length - 1 : 0;
obj[el?.dataKey?.label] = el?.data[indexDate][1];
obj[el?.dataKey?.label + '|ts'] = el?.data[indexDate][0];
if (el?.dataKey?.label === 'type') {
obj.deviceType = el?.data[indexDate][1];
}
});
return obj;
});
}
export function parseArray(input: DatasourceData[]): FormattedData[][] {
return _(input).groupBy(el => el?.datasource?.entityName)
.values().value().map((entityArray) =>
entityArray[0].data.map((el, i) => {
const obj: FormattedData = {
entityName: entityArray[0]?.datasource?.entityName,
entityId: entityArray[0]?.datasource?.entityId,
entityType: entityArray[0]?.datasource?.entityType,
$datasource: entityArray[0]?.datasource,
dsIndex: i,
time: el[0],
deviceType: null
};
entityArray.filter(e => e.data.length && e.data[i]).forEach(entity => {
obj[entity?.dataKey?.label] = entity?.data[i][1];
obj[entity?.dataKey?.label + '|ts'] = entity?.data[0][0];
if (entity?.dataKey?.label === 'type') {
obj.deviceType = entity?.data[0][1];
}
});
return obj;
})
);
}
export function parseFunction(source: any, params: string[] = ['def']): (...args: any[]) => any {
let res = null;
if (source?.length) {
try {
res = new Function(...params, source);
}
catch (err) {
res = null;
}
}
return res;
}
export function safeExecute(func: (...args: any[]) => any, params = []) {
let res = null;
if (func && typeof (func) === 'function') {
try {
res = func(...params);
}
catch (err) {
console.log('error in external function:', err);
res = null;
}
}
return res;
}
export function functionValueCalculator(useFunction: boolean, func: (...args: any[]) => any, params = [], defaultValue: any) {
let res;
if (useFunction && isDefined(func) && isFunction(func)) {
try {
res = func(...params);
if (!isDefinedAndNotNull(res) || res === '') {
res = defaultValue;
}
} catch (err) {
res = defaultValue;
console.log('error in external function:', err);
}
} else {
res = defaultValue;
}
return res;
}
export function calculateNewPointCoordinate(coordinate: number, imageSize: number): number {
let pointCoordinate = coordinate / imageSize;
if (pointCoordinate < 0) {
pointCoordinate = 0;
} else if (pointCoordinate > 1) {
pointCoordinate = 1;
}
return pointCoordinate;
}
export function createLoadingDiv(loadingText: string): JQuery<HTMLElement> {
return $(`
<div style="
z-index: 12;
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
flex-direction: column;
align-content: center;
align-items: center;
justify-content: center;
display: flex;
background: rgba(255,255,255,0.7);
font-size: 16px;
font-family: Roboto;
font-weight: 400;
text-transform: uppercase;
">
<span>${loadingText}</span>
</div>
`);
}

View File

@ -43,12 +43,14 @@ import { Observable, of } from 'rxjs';
import { Polyline } from './polyline';
import { Polygon } from './polygon';
import {
createLoadingDiv,
createTooltip,
} from '@home/components/widget/lib/maps/maps-utils';
import {
createLoadingDiv,
parseArray,
parseData,
safeExecute
} from '@home/components/widget/lib/maps/maps-utils';
} from '@home/components/widget/lib/maps/common-maps-utils';
import { WidgetContext } from '@home/models/widget-component.models';
import { deepClone, isDefinedAndNotNull, isEmptyStr, isString } from '@core/utils';
@ -141,7 +143,7 @@ export default abstract class LeafletMap {
const header = document.createElement('p');
header.appendChild(document.createTextNode('Select entity:'));
header.setAttribute('style', 'font-size: 14px; margin: 8px 0');
datasourcesList.append(header);
datasourcesList.appendChild(header);
this.datasources.forEach(ds => {
const dsItem = document.createElement('p');
dsItem.appendChild(document.createTextNode(ds.entityName));
@ -158,9 +160,9 @@ export default abstract class LeafletMap {
this.createMarker(ds.entityName, updatedEnttity, this.datasources, this.options);
});
};
datasourcesList.append(dsItem);
datasourcesList.appendChild(dsItem);
});
datasourcesList.append(document.createElement('br'));
datasourcesList.appendChild(document.createElement('br'));
const deleteBtn = document.createElement('a');
deleteBtn.appendChild(document.createTextNode('Discard changes'));
deleteBtn.onclick = () => {
@ -170,7 +172,7 @@ export default abstract class LeafletMap {
this.addMarkers.splice(markerIndex, 1);
}
};
datasourcesList.append(deleteBtn);
datasourcesList.appendChild(deleteBtn);
const popup = L.popup();
popup.setContent(datasourcesList);
newMarker.bindPopup(popup).openPopup();
@ -222,7 +224,7 @@ export default abstract class LeafletMap {
const header = document.createElement('p');
header.appendChild(document.createTextNode('Select entity:'));
header.setAttribute('style', 'font-size: 14px; margin: 8px 0');
datasourcesList.append(header);
datasourcesList.appendChild(header);
this.datasources.forEach(ds => {
const dsItem = document.createElement('p');
dsItem.appendChild(document.createTextNode(ds.entityName));
@ -238,9 +240,9 @@ export default abstract class LeafletMap {
this.deletePolygon(ds.entityName);
});
};
datasourcesList.append(dsItem);
datasourcesList.appendChild(dsItem);
});
datasourcesList.append(document.createElement('br'));
datasourcesList.appendChild(document.createElement('br'));
const deleteBtn = document.createElement('a');
deleteBtn.appendChild(document.createTextNode('Discard changes'));
deleteBtn.onclick = () => {
@ -250,7 +252,7 @@ export default abstract class LeafletMap {
this.addPolygons.splice(polygonIndex, 1);
}
};
datasourcesList.append(deleteBtn);
datasourcesList.appendChild(deleteBtn);
const popup = L.popup();
popup.setContent(datasourcesList);
newPolygon.bindPopup(popup).openPopup();
@ -289,7 +291,7 @@ export default abstract class LeafletMap {
if (!this.loadingDiv) {
this.loadingDiv = createLoadingDiv(this.ctx.translate.instant('common.loading'));
}
this.$container.append(this.loadingDiv[0]);
this.$container.appendChild(this.loadingDiv[0]);
} else {
if (this.loadingDiv) {
this.loadingDiv.remove();
@ -609,14 +611,13 @@ export default abstract class LeafletMap {
}
updatePoints(pointsData: FormattedData[][], getTooltip: (point: FormattedData) => string) {
if(pointsData.length) {
if (pointsData.length) {
if (this.points) {
this.map.removeLayer(this.points);
}
this.points = new FeatureGroup();
}
for(let i = 0; i < pointsData.length; i++) {
const pointsList = pointsData[i];
for (const pointsList of pointsData) {
pointsList.filter(pdata => !!this.convertPosition(pdata)).forEach(data => {
const point = L.circleMarker(this.convertPosition(data), {
color: this.options.pointColor,
@ -630,7 +631,7 @@ export default abstract class LeafletMap {
this.points.addLayer(point);
});
}
if(pointsData.length) {
if (pointsData.length) {
this.map.addLayer(this.points);
}
}

View File

@ -14,7 +14,6 @@
/// limitations under the License.
///
import { LatLngTuple } from 'leaflet';
import { Datasource } from '@app/shared/models/widget.models';
import { EntityType } from '@shared/models/entity-type.models';
import tinycolor from 'tinycolor2';
@ -47,7 +46,7 @@ export type MapSettings = {
provider?: MapProviders;
credentials?: any; // declare credentials format
gmApiKey?: string;
defaultCenterPosition?: LatLngTuple;
defaultCenterPosition?: [number, number];
markerClusteringSetting?;
useDefaultCenterPosition?: boolean;
gmDefaultMapType?: string;

View File

@ -15,9 +15,10 @@
///
import { JsonSettingsSchema } from '@shared/models/widget.models';
import { MapProviders } from './map-models';
import { MapProviders } from '@home/components/widget/lib/maps/map-models';
export interface MapWidgetInterface {
map?: any;
resize();
update();
onInit();

View File

@ -31,20 +31,25 @@ import {
routeMapSettingsSchema
} from './schemes';
import { MapWidgetInterface, MapWidgetStaticInterface } from './map-widget.interface';
import { addCondition, addGroupInfo, addToSchema, initSchema, mergeSchemes } from '@core/schema-utils';
import {
addCondition,
addGroupInfo,
addToSchema,
initSchema,
mergeSchemes
} from '@core/schema-utils';
import { WidgetContext } from '@app/modules/home/models/widget-component.models';
import { getDefCenterPosition, parseFunction, parseWithTranslation } from './maps-utils';
import { getDefCenterPosition, getProviderSchema, parseFunction, parseWithTranslation } from './common-maps-utils';
import { Datasource, DatasourceData, JsonSettingsSchema, WidgetActionDescriptor } from '@shared/models/widget.models';
import { EntityId } from '@shared/models/id/entity-id';
import { AttributeScope, DataKeyType, LatestTelemetry } from '@shared/models/telemetry/telemetry.models';
import { AttributeService } from '@core/http/attribute.service';
import { TranslateService } from '@ngx-translate/core';
import { UtilsService } from '@core/services/utils.service';
import _ from 'lodash';
import { EntityDataPageLink } from '@shared/models/query/query.models';
import { isDefined } from '@core/utils';
import { forkJoin, Observable, of } from 'rxjs';
import { providerSets } from '@home/components/widget/lib/maps/providers';
import { providerClass } from '@home/components/widget/lib/maps/providers';
// @dynamic
export class MapWidgetController implements MapWidgetInterface {
@ -70,7 +75,7 @@ export class MapWidgetController implements MapWidgetInterface {
this.settings.markerClick = this.getDescriptors('markerClick');
this.settings.polygonClick = this.getDescriptors('polygonClick');
const MapClass = providerSets[this.provider]?.MapClass;
const MapClass = providerClass[this.provider];
if (!MapClass) {
return;
}
@ -101,19 +106,7 @@ export class MapWidgetController implements MapWidgetInterface {
}
public static getProvidersSchema(mapProvider: MapProviders, ignoreImageMap = false) {
const providerSchema = _.cloneDeep(mapProviderSchema);
if (mapProvider) {
providerSchema.schema.properties.provider.default = mapProvider;
}
if (ignoreImageMap) {
providerSchema.form[0].items = providerSchema.form[0]?.items.filter(item => item.value !== 'image-map');
}
return mergeSchemes([providerSchema,
...Object.keys(providerSets)?.map(
(key: string) => {
const setting = providerSets[key];
return addCondition(setting?.schema, `model.provider === '${setting.name}'`);
})]);
return getProviderSchema(mapProvider, ignoreImageMap);
}
public static settingsSchema(mapProvider: MapProviders, drawRoutes: boolean): JsonSettingsSchema {

View File

@ -15,20 +15,8 @@
///
import L from 'leaflet';
import { FormattedData, MarkerSettings, PolygonSettings, PolylineSettings, ReplaceInfo } from './map-models';
import { Datasource, DatasourceData } from '@app/shared/models/widget.models';
import _ from 'lodash';
import { Observable, Observer, of } from 'rxjs';
import { map } from 'rxjs/operators';
import {
createLabelFromDatasource,
hashCode,
isDefined,
isDefinedAndNotNull, isFunction,
isNumber,
isUndefined,
padValue
} from '@core/utils';
import { MarkerSettings, PolygonSettings, PolylineSettings } from './map-models';
import { Datasource } from '@app/shared/models/widget.models';
export function createTooltip(target: L.Layer,
settings: MarkerSettings | PolylineSettings | PolygonSettings,
@ -68,400 +56,3 @@ export function bindPopupActions(popup: L.Popup, settings: MarkerSettings | Poly
}
});
}
export function getRatio(firsMoment: number, secondMoment: number, intermediateMoment: number): number {
return (intermediateMoment - firsMoment) / (secondMoment - firsMoment);
}
export function interpolateOnLineSegment(
pointA: FormattedData,
pointB: FormattedData,
latKeyName: string,
lngKeyName: string,
ratio: number
): { [key: string]: number } {
return {
[latKeyName]: (pointA[latKeyName] + (pointB[latKeyName] - pointA[latKeyName]) * ratio),
[lngKeyName]: (pointA[lngKeyName] + (pointB[lngKeyName] - pointA[lngKeyName]) * ratio)
};
}
export function findAngle(startPoint: FormattedData, endPoint: FormattedData, latKeyName: string, lngKeyName: string): number {
if (isUndefined(startPoint) || isUndefined(endPoint)) {
return 0;
}
let angle = -Math.atan2(endPoint[latKeyName] - startPoint[latKeyName], endPoint[lngKeyName] - startPoint[lngKeyName]);
angle = angle * 180 / Math.PI;
return parseInt(angle.toFixed(2), 10);
}
export function getDefCenterPosition(position) {
if (typeof (position) === 'string') {
return position.split(',');
}
if (typeof (position) === 'object') {
return position;
}
return [0, 0];
}
const imageAspectMap = {};
function imageLoader(imageUrl: string): Observable<HTMLImageElement> {
return new Observable((observer: Observer<HTMLImageElement>) => {
const image = document.createElement('img'); // support IE
image.style.position = 'absolute';
image.style.left = '-99999px';
image.style.top = '-99999px';
image.onload = () => {
observer.next(image);
document.body.removeChild(image);
observer.complete();
};
image.onerror = err => {
observer.error(err);
document.body.removeChild(image);
observer.complete();
};
document.body.appendChild(image);
image.src = imageUrl;
});
}
export function aspectCache(imageUrl: string): Observable<number> {
if (imageUrl?.length) {
const hash = hashCode(imageUrl);
let aspect = imageAspectMap[hash];
if (aspect) {
return of(aspect);
}
return imageLoader(imageUrl).pipe(map(image => {
aspect = image.width / image.height;
imageAspectMap[hash] = aspect;
return aspect;
}));
}
}
export type TranslateFunc = (key: string, defaultTranslation?: string) => string;
const varsRegex = /\${([^}]*)}/g;
const linkActionRegex = /<link-act name=['"]([^['"]*)['"]>([^<]*)<\/link-act>/g;
const buttonActionRegex = /<button-act name=['"]([^['"]*)['"]>([^<]*)<\/button-act>/g;
function createLinkElement(actionName: string, actionText: string): string {
return `<a href="javascript:void(0);" class="tb-custom-action" data-action-name=${actionName}>${actionText}</a>`;
}
function createButtonElement(actionName: string, actionText: string) {
return `<button mat-button class="tb-custom-action" data-action-name=${actionName}>${actionText}</button>`;
}
function parseTemplate(template: string, data: { $datasource?: Datasource, [key: string]: any },
translateFn?: TranslateFunc) {
let res = '';
try {
if (translateFn) {
template = translateFn(template);
}
template = createLabelFromDatasource(data.$datasource, template);
let match = /\${([^}]*)}/g.exec(template);
while (match !== null) {
const variable = match[0];
let label = match[1];
let valDec = 2;
const splitValues = label.split(':');
if (splitValues.length > 1) {
label = splitValues[0];
valDec = parseFloat(splitValues[1]);
}
if (label.startsWith('#')) {
const keyIndexStr = label.substring(1);
const n = Math.floor(Number(keyIndexStr));
if (String(n) === keyIndexStr && n >= 0) {
label = data.$datasource.dataKeys[n].label;
}
}
const value = data[label] || '';
let textValue: string;
if (isNumber(value)) {
textValue = padValue(value, valDec);
} else {
textValue = value;
}
template = template.replace(variable, textValue);
match = /\${([^}]*)}/g.exec(template);
}
let actionTags: string;
let actionText: string;
let actionName: string;
let action: string;
match = linkActionRegex.exec(template);
while (match !== null) {
[actionTags, actionName, actionText] = match;
action = createLinkElement(actionName, actionText);
template = template.replace(actionTags, action);
match = linkActionRegex.exec(template);
}
match = buttonActionRegex.exec(template);
while (match !== null) {
[actionTags, actionName, actionText] = match;
action = createButtonElement(actionName, actionText);
template = template.replace(actionTags, action);
match = buttonActionRegex.exec(template);
}
// const compiled = _.template(template);
// res = compiled(data);
res = template;
} catch (ex) {
console.log(ex, template);
}
return res;
}
export function processPattern(template: string, data: { $datasource?: Datasource, [key: string]: any }): Array<ReplaceInfo> {
const replaceInfo = [];
try {
const reg = /\${([^}]*)}/g;
let match = reg.exec(template);
while (match !== null) {
const variableInfo: ReplaceInfo = {
dataKeyName: '',
valDec: 2,
variable: ''
};
const variable = match[0];
let label = match[1];
let valDec = 2;
const splitValues = label.split(':');
if (splitValues.length > 1) {
label = splitValues[0];
valDec = parseFloat(splitValues[1]);
}
variableInfo.variable = variable;
variableInfo.valDec = valDec;
if (label.startsWith('#')) {
const keyIndexStr = label.substring(1);
const n = Math.floor(Number(keyIndexStr));
if (String(n) === keyIndexStr && n >= 0) {
variableInfo.dataKeyName = data.$datasource.dataKeys[n].label;
}
} else {
variableInfo.dataKeyName = label;
}
replaceInfo.push(variableInfo);
match = reg.exec(template);
}
} catch (ex) {
console.log(ex, template);
}
return replaceInfo;
}
export function fillPattern(markerLabelText: string, replaceInfoLabelMarker: Array<ReplaceInfo>, data: FormattedData) {
let text = createLabelFromDatasource(data.$datasource, markerLabelText);
if (replaceInfoLabelMarker) {
for (const variableInfo of replaceInfoLabelMarker) {
let txtVal = '';
if (variableInfo.dataKeyName && isDefinedAndNotNull(data[variableInfo.dataKeyName])) {
const varData = data[variableInfo.dataKeyName];
if (isNumber(varData)) {
txtVal = padValue(varData, variableInfo.valDec);
} else {
txtVal = varData;
}
}
text = text.replace(variableInfo.variable, txtVal);
}
}
return text;
}
function prepareProcessPattern(template: string, translateFn?: TranslateFunc): string {
if (translateFn) {
template = translateFn(template);
}
let actionTags: string;
let actionText: string;
let actionName: string;
let action: string;
let match = linkActionRegex.exec(template);
while (match !== null) {
[actionTags, actionName, actionText] = match;
action = createLinkElement(actionName, actionText);
template = template.replace(actionTags, action);
match = linkActionRegex.exec(template);
}
match = buttonActionRegex.exec(template);
while (match !== null) {
[actionTags, actionName, actionText] = match;
action = createButtonElement(actionName, actionText);
template = template.replace(actionTags, action);
match = buttonActionRegex.exec(template);
}
return template;
}
export const parseWithTranslation = {
translateFn: null,
translate(key: string, defaultTranslation?: string): string {
if (this.translateFn) {
return this.translateFn(key, defaultTranslation);
} else {
throw console.error('Translate not assigned');
}
},
parseTemplate(template: string, data: object, forceTranslate = false): string {
return parseTemplate(forceTranslate ? this.translate(template) : template, data, this.translate.bind(this));
},
prepareProcessPattern(template: string, forceTranslate = false): string {
return prepareProcessPattern(forceTranslate ? this.translate(template) : template, this.translate.bind(this));
},
setTranslate(translateFn: TranslateFunc) {
this.translateFn = translateFn;
}
};
export function parseData(input: DatasourceData[]): FormattedData[] {
return _(input).groupBy(el => el?.datasource?.entityName)
.values().value().map((entityArray, i) => {
const obj: FormattedData = {
entityName: entityArray[0]?.datasource?.entityName,
entityId: entityArray[0]?.datasource?.entityId,
entityType: entityArray[0]?.datasource?.entityType,
$datasource: entityArray[0]?.datasource,
dsIndex: i,
deviceType: null
};
entityArray.filter(el => el.data.length).forEach(el => {
const indexDate = el?.data?.length ? el.data.length - 1 : 0;
obj[el?.dataKey?.label] = el?.data[indexDate][1];
obj[el?.dataKey?.label + '|ts'] = el?.data[indexDate][0];
if (el?.dataKey?.label === 'type') {
obj.deviceType = el?.data[indexDate][1];
}
});
return obj;
});
}
export function parseArray(input: DatasourceData[]): FormattedData[][] {
return _(input).groupBy(el => el?.datasource?.entityName)
.values().value().map((entityArray) =>
entityArray[0].data.map((el, i) => {
const obj: FormattedData = {
entityName: entityArray[0]?.datasource?.entityName,
entityId: entityArray[0]?.datasource?.entityId,
entityType: entityArray[0]?.datasource?.entityType,
$datasource: entityArray[0]?.datasource,
dsIndex: i,
time: el[0],
deviceType: null
};
entityArray.filter(e => e.data.length && e.data[i]).forEach(entity => {
obj[entity?.dataKey?.label] = entity?.data[i][1];
obj[entity?.dataKey?.label + '|ts'] = entity?.data[0][0];
if (entity?.dataKey?.label === 'type') {
obj.deviceType = entity?.data[0][1];
}
});
return obj;
})
);
}
export function parseFunction(source: any, params: string[] = ['def']): (...args: any[]) => any {
let res = null;
if (source?.length) {
try {
res = new Function(...params, source);
}
catch (err) {
res = null;
}
}
return res;
}
export function safeExecute(func: (...args: any[]) => any, params = []) {
let res = null;
if (func && typeof (func) === 'function') {
try {
res = func(...params);
}
catch (err) {
console.log('error in external function:', err);
res = null;
}
}
return res;
}
export function functionValueCalculator(useFunction: boolean, func: (...args: any[]) => any, params = [], defaultValue: any) {
let res;
if (useFunction && isDefined(func) && isFunction(func)) {
try {
res = func(...params);
if (!isDefinedAndNotNull(res) || res === '') {
res = defaultValue;
}
} catch (err) {
res = defaultValue;
console.log('error in external function:', err);
}
} else {
res = defaultValue;
}
return res;
}
export function calculateNewPointCoordinate(coordinate: number, imageSize: number): number {
let pointCoordinate = coordinate / imageSize;
if (pointCoordinate < 0) {
pointCoordinate = 0;
} else if (pointCoordinate > 1) {
pointCoordinate = 1;
}
return pointCoordinate;
}
export function createLoadingDiv(loadingText: string): JQuery<HTMLElement> {
return $(`
<div style="
z-index: 12;
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
flex-direction: column;
align-content: center;
align-items: center;
justify-content: center;
display: flex;
background: rgba(255,255,255,0.7);
font-size: 16px;
font-family: Roboto;
font-weight: 400;
text-transform: uppercase;
">
<span>${loadingText}</span>
</div>
`);
}

View File

@ -17,14 +17,16 @@
import L, { Icon, LeafletMouseEvent } from 'leaflet';
import { FormattedData, MarkerSettings } from './map-models';
import {
aspectCache,
bindPopupActions,
createTooltip,
} from './maps-utils';
import {
aspectCache,
fillPattern,
parseWithTranslation,
processPattern,
safeExecute
} from './maps-utils';
} from './common-maps-utils';
import tinycolor from 'tinycolor2';
import { isDefined, isDefinedAndNotNull } from '@core/utils';
import LeafletMap from './leaflet-map';

View File

@ -15,7 +15,8 @@
///
import L, { LatLngExpression, LeafletMouseEvent } from 'leaflet';
import { createTooltip, functionValueCalculator, parseWithTranslation, safeExecute } from './maps-utils';
import { createTooltip } from './maps-utils';
import { functionValueCalculator, parseWithTranslation, safeExecute } from './common-maps-utils';
import 'leaflet-editable/src/Leaflet.Editable';
import { FormattedData, PolygonSettings } from './map-models';

View File

@ -18,7 +18,7 @@ import L, { PolylineDecoratorOptions } from 'leaflet';
import 'leaflet-polylinedecorator';
import { FormattedData, PolylineSettings } from './map-models';
import { functionValueCalculator, safeExecute } from '@home/components/widget/lib/maps/maps-utils';
import { functionValueCalculator } from '@home/components/widget/lib/maps/common-maps-utils';
export class Polyline {

View File

@ -19,7 +19,7 @@ import LeafletMap from '../leaflet-map';
import { MapImage, PosFuncton, UnitedMapSettings } from '../map-models';
import { Observable, ReplaySubject } from 'rxjs';
import { filter, map, mergeMap } from 'rxjs/operators';
import { aspectCache, calculateNewPointCoordinate, parseFunction } from '@home/components/widget/lib/maps/maps-utils';
import { aspectCache, calculateNewPointCoordinate, parseFunction } from '@home/components/widget/lib/maps/common-maps-utils';
import { WidgetContext } from '@home/models/widget-component.models';
import { DataSet, DatasourceType, widgetType } from '@shared/models/widget.models';
import { DataKeyType } from '@shared/models/telemetry/telemetry.models';

View File

@ -14,11 +14,6 @@
/// limitations under the License.
///
import {
googleMapSettingsSchema, hereMapSettingsSchema, imageMapSettingsSchema,
openstreetMapSettingsSchema,
tencentMapSettingsSchema
} from '@home/components/widget/lib/maps/schemes';
import { OpenStreetMap } from './openstreet-map';
import { TencentMap } from './tencent-map';
import { GoogleMap } from './google-map';
@ -26,38 +21,11 @@ import { HEREMap } from './here-map';
import { ImageMap } from './image-map';
import { Type } from '@angular/core';
import LeafletMap from '@home/components/widget/lib/maps/leaflet-map';
import { JsonSettingsSchema } from '@shared/models/widget.models';
interface IProvider {
MapClass: Type<LeafletMap>;
schema: JsonSettingsSchema;
name: string;
}
export const providerSets: { [key: string]: IProvider } = {
'openstreet-map': {
MapClass: OpenStreetMap,
schema: openstreetMapSettingsSchema,
name: 'openstreet-map'
},
'tencent-map': {
MapClass: TencentMap,
schema: tencentMapSettingsSchema,
name: 'tencent-map'
},
'google-map': {
MapClass: GoogleMap,
schema: googleMapSettingsSchema,
name: 'google-map'
},
here: {
MapClass: HEREMap,
schema: hereMapSettingsSchema,
name: 'here'
},
'image-map': {
MapClass: ImageMap,
schema: imageMapSettingsSchema,
name: 'image-map'
}
export const providerClass: { [key: string]: Type<LeafletMap> } = {
'openstreet-map': OpenStreetMap,
'tencent-map': TencentMap,
'google-map': GoogleMap,
here: HEREMap,
'image-map': ImageMap
};

View File

@ -14,6 +14,8 @@
/// limitations under the License.
///
import { JsonSettingsSchema } from '@shared/models/widget.models';
export const googleMapSettingsSchema =
{
schema: {
@ -1096,3 +1098,31 @@ export const tripAnimationSchema = {
]
}]
};
interface IProvider {
schema: JsonSettingsSchema;
name: string;
}
export const providerSets: { [key: string]: IProvider } = {
'openstreet-map': {
schema: openstreetMapSettingsSchema,
name: 'openstreet-map'
},
'tencent-map': {
schema: tencentMapSettingsSchema,
name: 'tencent-map'
},
'google-map': {
schema: googleMapSettingsSchema,
name: 'google-map'
},
here: {
schema: hereMapSettingsSchema,
name: 'here'
},
'image-map': {
schema: imageMapSettingsSchema,
name: 'image-map'
}
};

View File

@ -21,7 +21,7 @@ import { UtilsService } from '@core/services/utils.service';
import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state';
import { isDefined, isNumber } from '@core/utils';
import { CanvasDigitalGauge, CanvasDigitalGaugeOptions } from '@home/components/widget/lib/canvas-digital-gauge';
import { CanvasDigitalGaugeOptions } from '@home/components/widget/lib/canvas-digital-gauge';
import * as tinycolor_ from 'tinycolor2';
import { ResizeObserver } from '@juggle/resize-observer';
import GenericOptions = CanvasGauges.GenericOptions;
@ -102,7 +102,7 @@ export class KnobComponent extends PageComponent implements OnInit, OnDestroy {
private textMeasure: JQuery<HTMLElement>;
private canvasBarElement: HTMLElement;
private canvasBar: CanvasDigitalGauge;
private canvasBar: any; // CanvasDigitalGauge;
private knobResize$: ResizeObserver;
@ -165,7 +165,6 @@ export class KnobComponent extends PageComponent implements OnInit, OnDestroy {
animation: false
};
this.canvasBar = new CanvasDigitalGauge(canvasBarData).draw();
this.knob.on('click', (e) => {
if (this.moving) {
@ -277,9 +276,6 @@ export class KnobComponent extends PageComponent implements OnInit, OnDestroy {
});
const initialValue = isDefined(settings.initialValue) ? settings.initialValue : this.minValue;
this.setValue(initialValue);
const subscription = this.ctx.defaultSubscription;
const rpcEnabled = subscription.rpcEnabled;
@ -297,13 +293,22 @@ export class KnobComponent extends PageComponent implements OnInit, OnDestroy {
if (settings.setValueMethod && settings.setValueMethod.length) {
this.setValueMethod = settings.setValueMethod;
}
if (!rpcEnabled) {
this.onError('Target device is not set!');
} else {
if (!this.isSimulated) {
this.rpcRequestValue();
import('@home/components/widget/lib/canvas-digital-gauge').then(
(gauge) => {
this.canvasBar = new gauge.CanvasDigitalGauge(canvasBarData).draw();
const initialValue = isDefined(settings.initialValue) ? settings.initialValue : this.minValue;
this.setValue(initialValue);
if (!rpcEnabled) {
this.onError('Target device is not set!');
} else {
if (!this.isSimulated) {
this.rpcRequestValue();
}
}
}
}
);
}
private degreeToRatio(degree: number): number {
@ -328,7 +333,9 @@ export class KnobComponent extends PageComponent implements OnInit, OnDestroy {
const height = this.knobContainer.height();
const size = Math.min(width, height);
this.knob.css({width: size, height: size});
this.canvasBar.update({width: size, height: size} as GenericOptions);
if (this.canvasBar) {
this.canvasBar.update({width: size, height: size} as GenericOptions);
}
this.setFontSize(this.knobTitle, this.title, this.knobTitleContainer.height(), this.knobTitleContainer.width());
this.setFontSize(this.knobError, this.error, this.knobErrorContainer.height(), this.knobErrorContainer.width());
const minmaxHeight = this.knobMinmaxContainer.height();

View File

@ -546,7 +546,7 @@ class TimeseriesDatasource implements DataSource<TimeseriesRow> {
}
}
} else {
rows = Object.values(rowsMap);
rows = Object.keys(rowsMap).map(itm => rowsMap[itm]);
}
return rows;
}

View File

@ -27,28 +27,28 @@ import {
SecurityContext,
ViewChild
} from '@angular/core';
import { MapWidgetController, TbMapWidgetV2 } from '../lib/maps/map-widget2';
import { FormattedData, MapProviders, TripAnimationSettings } from '../lib/maps/map-models';
import { FormattedData, MapProviders, TripAnimationSettings } from '@home/components/widget/lib/maps/map-models';
import { addCondition, addGroupInfo, addToSchema, initSchema } from '@app/core/schema-utils';
import { mapPolygonSchema, pathSchema, pointSchema, tripAnimationSchema } from '../lib/maps/schemes';
import { mapPolygonSchema, pathSchema, pointSchema, tripAnimationSchema } from '@home/components/widget/lib/maps/schemes';
import { DomSanitizer } from '@angular/platform-browser';
import { WidgetContext } from '@app/modules/home/models/widget-component.models';
import {
findAngle,
findAngle, getProviderSchema,
getRatio,
interpolateOnLineSegment,
parseArray,
parseFunction,
parseWithTranslation,
safeExecute
} from '../lib/maps/maps-utils';
} from '@home/components/widget/lib/maps/common-maps-utils';
import { JsonSettingsSchema, WidgetConfig } from '@shared/models/widget.models';
import moment from 'moment';
import { isUndefined } from '@core/utils';
import { ResizeObserver } from '@juggle/resize-observer';
import { MapWidgetInterface } from '@home/components/widget/lib/maps/map-widget.interface';
interface dataMap {
[key: string] : FormattedData
interface DataMap {
[key: string]: FormattedData;
}
@Component({
@ -67,7 +67,7 @@ export class TripAnimationComponent implements OnInit, AfterViewInit, OnDestroy
@ViewChild('map') mapContainer;
mapWidget: MapWidgetController;
mapWidget: MapWidgetInterface;
historicalData: FormattedData[][];
normalizationStep: number;
interpolatedTimeData = [];
@ -85,7 +85,7 @@ export class TripAnimationComponent implements OnInit, AfterViewInit, OnDestroy
static getSettingsSchema(): JsonSettingsSchema {
const schema = initSchema();
addToSchema(schema, TbMapWidgetV2.getProvidersSchema(null, true));
addToSchema(schema, getProviderSchema(null, true));
addGroupInfo(schema, 'Map Provider Settings');
addToSchema(schema, tripAnimationSchema);
addGroupInfo(schema, 'Trip Animation Settings');
@ -106,7 +106,7 @@ export class TripAnimationComponent implements OnInit, AfterViewInit, OnDestroy
buttonColor: tinycolor(this.widgetConfig.color).setAlpha(0.54).toRgbString(),
disabledButtonColor: tinycolor(this.widgetConfig.color).setAlpha(0.3).toRgbString(),
rotationAngle: 0
}
};
this.settings = { ...settings, ...this.ctx.settings };
this.useAnchors = this.settings.showPoints && this.settings.usePointAsAnchor;
this.settings.pointAsAnchorFunction = parseFunction(this.settings.pointAsAnchorFunction, ['data', 'dsData', 'dsIndex']);
@ -122,15 +122,19 @@ export class TripAnimationComponent implements OnInit, AfterViewInit, OnDestroy
}
this.mapWidget.map.map?.invalidateSize();
this.cd.detectChanges();
}
};
}
ngAfterViewInit() {
this.mapWidget = new MapWidgetController(MapProviders.openstreet, false, this.ctx, this.mapContainer.nativeElement);
this.mapResize$ = new ResizeObserver(() => {
this.mapWidget.resize();
});
this.mapResize$.observe(this.mapContainer.nativeElement);
import('@home/components/widget/lib/maps/map-widget2').then(
(mod) => {
this.mapWidget = new mod.MapWidgetController(MapProviders.openstreet, false, this.ctx, this.mapContainer.nativeElement);
this.mapResize$ = new ResizeObserver(() => {
this.mapWidget.resize();
});
this.mapResize$.observe(this.mapContainer.nativeElement);
}
);
}
ngOnDestroy() {
@ -142,8 +146,8 @@ export class TripAnimationComponent implements OnInit, AfterViewInit, OnDestroy
timeUpdated(time: number) {
this.currentTime = time;
const currentPosition = this.interpolatedTimeData
.map(dataSource => dataSource[time])
for(let j = 0; j < this.interpolatedTimeData.length; j++) {
.map(dataSource => dataSource[time]);
for (let j = 0; j < this.interpolatedTimeData.length; j++) {
if (isUndefined(currentPosition[j])) {
const timePoints = Object.keys(this.interpolatedTimeData[j]).map(item => parseInt(item, 10));
for (let i = 1; i < timePoints.length; i++) {
@ -155,13 +159,13 @@ export class TripAnimationComponent implements OnInit, AfterViewInit, OnDestroy
...beforePosition,
time,
...interpolateOnLineSegment(beforePosition, afterPosition, this.settings.latKeyName, this.settings.lngKeyName, ratio)
}
};
break;
}
}
}
}
for(let j = 0; j < this.interpolatedTimeData.length; j++) {
for (let j = 0; j < this.interpolatedTimeData.length; j++) {
if (isUndefined(currentPosition[j])) {
currentPosition[j] = this.calculateLastPoints(this.interpolatedTimeData[j], time);
}
@ -179,7 +183,7 @@ export class TripAnimationComponent implements OnInit, AfterViewInit, OnDestroy
}
this.mapWidget.map.updateMarkers(currentPosition, true, (trip) => {
this.activeTrip = trip;
this.timeUpdated(this.currentTime)
this.timeUpdated(this.currentTime);
});
}
}
@ -187,14 +191,14 @@ export class TripAnimationComponent implements OnInit, AfterViewInit, OnDestroy
setActiveTrip() {
}
private calculateLastPoints(dataSource: dataMap, time: number): FormattedData {
private calculateLastPoints(dataSource: DataMap, time: number): FormattedData {
const timeArr = Object.keys(dataSource);
let index = timeArr.findIndex((dtime, index) => {
let index = timeArr.findIndex((dtime) => {
return Number(dtime) >= time;
});
if(index !== -1) {
if(Number(timeArr[index]) !== time && index !== 0) {
if (index !== -1) {
if (Number(timeArr[index]) !== time && index !== 0) {
index--;
}
} else {
@ -210,7 +214,7 @@ export class TripAnimationComponent implements OnInit, AfterViewInit, OnDestroy
this.maxTime = dataSource[dataSource.length - 1]?.time || -Infinity;
this.interpolatedTimeData[index] = this.interpolateArray(dataSource);
});
if(!this.activeTrip){
if (!this.activeTrip) {
this.activeTrip = this.interpolatedTimeData.map(dataSource => dataSource[this.minTime]).filter(ds => ds)[0];
}
if (this.useAnchors) {
@ -230,7 +234,7 @@ export class TripAnimationComponent implements OnInit, AfterViewInit, OnDestroy
private calcMainTooltip(points: FormattedData[]): void {
const tooltips = [];
for (let point of points) {
for (const point of points) {
tooltips.push(this.sanitizer.sanitize(SecurityContext.HTML, this.calcTooltip(point)));
}
this.mainTooltips = tooltips;
@ -259,7 +263,7 @@ export class TripAnimationComponent implements OnInit, AfterViewInit, OnDestroy
}
const timeStamp = Object.keys(result);
for (let i = 0; i < timeStamp.length - 1; i++) {
result[timeStamp[i]].rotationAngle += findAngle(result[timeStamp[i]], result[timeStamp[i + 1]], latKeyName, lngKeyName)
result[timeStamp[i]].rotationAngle += findAngle(result[timeStamp[i]], result[timeStamp[i + 1]], latKeyName, lngKeyName);
}
return result;
}

View File

@ -17,7 +17,7 @@
import { Inject, Injectable, Optional, Type } from '@angular/core';
import { DynamicComponentFactoryService } from '@core/services/dynamic-component-factory.service';
import { WidgetService } from '@core/http/widget.service';
import { forkJoin, Observable, of, ReplaySubject, Subject, throwError } from 'rxjs';
import { forkJoin, from, Observable, of, ReplaySubject, Subject, throwError } from 'rxjs';
import {
ErrorWidgetType,
MissingWidgetType,
@ -30,7 +30,7 @@ import cssjs from '@core/css/css';
import { UtilsService } from '@core/services/utils.service';
import { ResourcesService } from '@core/services/resources.service';
import { Widget, widgetActionSources, WidgetControllerDescriptor, WidgetType } from '@shared/models/widget.models';
import { catchError, map, mergeMap, switchMap } from 'rxjs/operators';
import { catchError, map, mergeMap, switchMap, tap } from 'rxjs/operators';
import { isFunction, isUndefined } from '@core/utils';
import { TranslateService } from '@ngx-translate/core';
import { DynamicWidgetComponent } from '@home/components/widget/dynamic-widget.component';
@ -42,6 +42,10 @@ import { WidgetTypeId } from '@app/shared/models/id/widget-type-id';
import { TenantId } from '@app/shared/models/id/tenant-id';
import { SharedModule } from '@shared/shared.module';
import { MODULES_MAP } from '@shared/public-api';
import * as tinycolor_ from 'tinycolor2';
import moment from 'moment';
const tinycolor = tinycolor_;
// @dynamic
@Injectable()
@ -106,20 +110,77 @@ export class WidgetComponentService {
}
const initSubject = new ReplaySubject();
this.init$ = initSubject.asObservable();
const loadDefaultWidgetInfoTasks = [
this.loadWidgetResources(this.missingWidgetType, 'global-widget-missing-type', [SharedModule, WidgetComponentsModule]),
this.loadWidgetResources(this.errorWidgetType, 'global-widget-error-type', [SharedModule, WidgetComponentsModule]),
];
forkJoin(loadDefaultWidgetInfoTasks).subscribe(
(window as any).tinycolor = tinycolor;
(window as any).cssjs = cssjs;
(window as any).moment = moment;
const widgetModulesTasks: Observable<any>[] = [];
widgetModulesTasks.push(from(import('@home/components/widget/lib/flot-widget')).pipe(
tap((mod) => {
(window as any).TbFlot = mod.TbFlot;
}))
);
widgetModulesTasks.push(from(import('@home/components/widget/lib/analogue-compass')).pipe(
tap((mod) => {
(window as any).TbAnalogueCompass = mod.TbAnalogueCompass;
}))
);
widgetModulesTasks.push(from(import('@home/components/widget/lib/analogue-radial-gauge')).pipe(
tap((mod) => {
(window as any).TbAnalogueRadialGauge = mod.TbAnalogueRadialGauge;
}))
);
widgetModulesTasks.push(from(import('@home/components/widget/lib/analogue-linear-gauge')).pipe(
tap((mod) => {
(window as any).TbAnalogueLinearGauge = mod.TbAnalogueLinearGauge;
}))
);
widgetModulesTasks.push(from(import('@home/components/widget/lib/digital-gauge')).pipe(
tap((mod) => {
(window as any).TbCanvasDigitalGauge = mod.TbCanvasDigitalGauge;
}))
);
widgetModulesTasks.push(from(import('@home/components/widget/lib/maps/map-widget2')).pipe(
tap((mod) => {
(window as any).TbMapWidgetV2 = mod.TbMapWidgetV2;
}))
);
widgetModulesTasks.push(from(import('@home/components/widget/trip-animation/trip-animation.component')).pipe(
tap((mod) => {
(window as any).TbTripAnimationWidget = mod.TbTripAnimationWidget;
}))
);
forkJoin(widgetModulesTasks).subscribe(
() => {
initSubject.next();
const loadDefaultWidgetInfoTasks = [
this.loadWidgetResources(this.missingWidgetType, 'global-widget-missing-type', [SharedModule, WidgetComponentsModule]),
this.loadWidgetResources(this.errorWidgetType, 'global-widget-error-type', [SharedModule, WidgetComponentsModule]),
];
forkJoin(loadDefaultWidgetInfoTasks).subscribe(
() => {
initSubject.next();
},
(e) => {
let errorMessages = ['Failed to load default widget types!'];
if (e && e.length) {
errorMessages = errorMessages.concat(e);
}
console.error('Failed to load default widget types!');
initSubject.error({
widgetInfo: this.errorWidgetType,
errorMessages
});
}
);
},
(e) => {
let errorMessages = ['Failed to load default widget types!'];
let errorMessages = ['Failed to load widget modules!'];
if (e && e.length) {
errorMessages = errorMessages.concat(e);
}
console.error('Failed to load default widget types!');
console.error('Failed to load widget modules!');
initSubject.error({
widgetInfo: this.errorWidgetType,
errorMessages

View File

@ -32,7 +32,8 @@ import { NULL_UUID } from '@shared/models/id/has-uuid';
import { Hotkey } from 'angular2-hotkeys';
import { TranslateService } from '@ngx-translate/core';
import { getCurrentIsLoading } from '@app/core/interceptors/load.selectors';
import * as ace from 'ace-builds';
import { Ace } from 'ace-builds';
import { getAce, Range } from '@shared/models/ace/ace.models';
import { css_beautify, html_beautify } from 'js-beautify';
import { CancelAnimationFrame, RafService } from '@core/services/raf.service';
import { WINDOW } from '@core/services/window.service';
@ -44,10 +45,12 @@ import {
SaveWidgetTypeAsDialogComponent,
SaveWidgetTypeAsDialogResult
} from '@home/pages/widget/save-widget-type-as-dialog.component';
import { Subscription } from 'rxjs';
import { forkJoin, from, Subscription } from 'rxjs';
import { ResizeObserver } from '@juggle/resize-observer';
import Timeout = NodeJS.Timeout;
import { widgetEditorCompleter } from '@home/pages/widget/widget-editor.models';
import { Observable } from 'rxjs/internal/Observable';
import { map, tap } from 'rxjs/operators';
// @dynamic
@Component({
@ -119,13 +122,13 @@ export class WidgetEditorComponent extends PageComponent implements OnInit, OnDe
javascriptFullscreen = false;
iFrameFullscreen = false;
aceEditors: ace.Ace.Editor[] = [];
aceEditors: Ace.Editor[] = [];
editorsResizeCafs: {[editorId: string]: CancelAnimationFrame} = {};
htmlEditor: ace.Ace.Editor;
cssEditor: ace.Ace.Editor;
jsonSettingsEditor: ace.Ace.Editor;
dataKeyJsonSettingsEditor: ace.Ace.Editor;
jsEditor: ace.Ace.Editor;
htmlEditor: Ace.Editor;
cssEditor: Ace.Editor;
jsonSettingsEditor: Ace.Editor;
dataKeyJsonSettingsEditor: Ace.Editor;
jsEditor: Ace.Editor;
aceResize$: ResizeObserver;
onWindowMessageListener = this.onWindowMessage.bind(this);
@ -277,51 +280,85 @@ export class WidgetEditorComponent extends PageComponent implements OnInit, OnDe
this.onAceEditorResize(editor);
});
});
this.htmlEditor = this.createAceEditor(this.htmlInputElmRef, 'html');
this.htmlEditor.on('input', () => {
const editorValue = this.htmlEditor.getValue();
if (this.widget.templateHtml !== editorValue) {
this.widget.templateHtml = editorValue;
this.isDirty = true;
const editorsObservables: Observable<any>[] = [];
editorsObservables.push(this.createAceEditor(this.htmlInputElmRef, 'html').pipe(
tap((editor) => {
this.htmlEditor = editor;
this.htmlEditor.on('input', () => {
const editorValue = this.htmlEditor.getValue();
if (this.widget.templateHtml !== editorValue) {
this.widget.templateHtml = editorValue;
this.isDirty = true;
}
});
})
));
editorsObservables.push(this.createAceEditor(this.cssInputElmRef, 'css').pipe(
tap((editor) => {
this.cssEditor = editor;
this.cssEditor.on('input', () => {
const editorValue = this.cssEditor.getValue();
if (this.widget.templateCss !== editorValue) {
this.widget.templateCss = editorValue;
this.isDirty = true;
}
});
})
));
editorsObservables.push(this.createAceEditor(this.settingsJsonInputElmRef, 'json').pipe(
tap((editor) => {
this.jsonSettingsEditor = editor;
this.jsonSettingsEditor.on('input', () => {
const editorValue = this.jsonSettingsEditor.getValue();
if (this.widget.settingsSchema !== editorValue) {
this.widget.settingsSchema = editorValue;
this.isDirty = true;
}
});
})
));
editorsObservables.push(this.createAceEditor(this.dataKeySettingsJsonInputElmRef, 'json').pipe(
tap((editor) => {
this.dataKeyJsonSettingsEditor = editor;
this.dataKeyJsonSettingsEditor.on('input', () => {
const editorValue = this.dataKeyJsonSettingsEditor.getValue();
if (this.widget.dataKeySettingsSchema !== editorValue) {
this.widget.dataKeySettingsSchema = editorValue;
this.isDirty = true;
}
});
})
));
editorsObservables.push(this.createAceEditor(this.javascriptInputElmRef, 'javascript').pipe(
tap((editor) => {
this.jsEditor = editor;
this.jsEditor.on('input', () => {
const editorValue = this.jsEditor.getValue();
if (this.widget.controllerScript !== editorValue) {
this.widget.controllerScript = editorValue;
this.isDirty = true;
}
});
this.jsEditor.on('change', () => {
this.cleanupJsErrors();
});
this.jsEditor.completers = [widgetEditorCompleter, ...(this.jsEditor.completers || [])];
})
));
forkJoin(editorsObservables).subscribe(
() => {
this.setAceEditorValues();
}
});
this.cssEditor = this.createAceEditor(this.cssInputElmRef, 'css');
this.cssEditor.on('input', () => {
const editorValue = this.cssEditor.getValue();
if (this.widget.templateCss !== editorValue) {
this.widget.templateCss = editorValue;
this.isDirty = true;
}
});
this.jsonSettingsEditor = this.createAceEditor(this.settingsJsonInputElmRef, 'json');
this.jsonSettingsEditor.on('input', () => {
const editorValue = this.jsonSettingsEditor.getValue();
if (this.widget.settingsSchema !== editorValue) {
this.widget.settingsSchema = editorValue;
this.isDirty = true;
}
});
this.dataKeyJsonSettingsEditor = this.createAceEditor(this.dataKeySettingsJsonInputElmRef, 'json');
this.dataKeyJsonSettingsEditor.on('input', () => {
const editorValue = this.dataKeyJsonSettingsEditor.getValue();
if (this.widget.dataKeySettingsSchema !== editorValue) {
this.widget.dataKeySettingsSchema = editorValue;
this.isDirty = true;
}
});
this.jsEditor = this.createAceEditor(this.javascriptInputElmRef, 'javascript');
this.jsEditor.on('input', () => {
const editorValue = this.jsEditor.getValue();
if (this.widget.controllerScript !== editorValue) {
this.widget.controllerScript = editorValue;
this.isDirty = true;
}
});
this.jsEditor.on('change', () => {
this.cleanupJsErrors();
});
this.jsEditor.completers = [widgetEditorCompleter, ...(this.jsEditor.completers || [])];
this.setAceEditorValues();
);
}
private setAceEditorValues() {
@ -332,9 +369,9 @@ export class WidgetEditorComponent extends PageComponent implements OnInit, OnDe
this.jsEditor.setValue(this.widget.controllerScript ? this.widget.controllerScript : '', -1);
}
private createAceEditor(editorElementRef: ElementRef, mode: string): ace.Ace.Editor {
private createAceEditor(editorElementRef: ElementRef, mode: string): Observable<Ace.Editor> {
const editorElement = editorElementRef.nativeElement;
let editorOptions: Partial<ace.Ace.EditorOptions> = {
let editorOptions: Partial<Ace.EditorOptions> = {
mode: `ace/mode/${mode}`,
showGutter: true,
showPrintMargin: true
@ -345,14 +382,18 @@ export class WidgetEditorComponent extends PageComponent implements OnInit, OnDe
enableLiveAutocompletion: true
};
editorOptions = {...editorOptions, ...advancedOptions};
const aceEditor = ace.edit(editorElement, editorOptions);
aceEditor.session.setUseWrapMode(true);
this.aceEditors.push(aceEditor);
this.aceResize$.observe(editorElement);
return aceEditor;
return getAce().pipe(
map((ace) => {
const aceEditor = ace.edit(editorElement, editorOptions);
aceEditor.session.setUseWrapMode(true);
this.aceEditors.push(aceEditor);
this.aceResize$.observe(editorElement);
return aceEditor;
})
);
}
private onAceEditorResize(aceEditor: ace.Ace.Editor) {
private onAceEditorResize(aceEditor: Ace.Editor) {
if (this.editorsResizeCafs[aceEditor.id]) {
this.editorsResizeCafs[aceEditor.id]();
delete this.editorsResizeCafs[aceEditor.id];
@ -443,11 +484,11 @@ export class WidgetEditorComponent extends PageComponent implements OnInit, OnDe
if (details.columnNumber) {
column = details.columnNumber;
}
const errorMarkerId = this.jsEditor.session.addMarker(new ace.Range(line, 0, line, Infinity),
const errorMarkerId = this.jsEditor.session.addMarker(new Range(line, 0, line, Infinity),
'ace_active-line', 'screenLine');
this.errorMarkers.push(errorMarkerId);
const annotations = this.jsEditor.session.getAnnotations();
const errorAnnotation: ace.Ace.Annotation = {
const errorAnnotation: Ace.Annotation = {
row: line,
column,
text: details.message,

View File

@ -25,7 +25,8 @@ import {
ViewEncapsulation
} from '@angular/core';
import { ControlValueAccessor, FormControl, NG_VALIDATORS, NG_VALUE_ACCESSOR, Validator } from '@angular/forms';
import * as ace from 'ace-builds';
import { Ace } from 'ace-builds';
import { getAce, Range } from '@shared/models/ace/ace.models';
import { coerceBooleanProperty } from '@angular/cdk/coercion';
import { ActionNotificationHide, ActionNotificationShow } from '@core/notification/notification.actions';
import { Store } from '@ngrx/store';
@ -60,7 +61,7 @@ export class JsFuncComponent implements OnInit, OnDestroy, ControlValueAccessor,
@ViewChild('javascriptEditor', {static: true})
javascriptEditorElmRef: ElementRef;
private jsEditor: ace.Ace.Editor;
private jsEditor: Ace.Editor;
private editorsResizeCaf: CancelAnimationFrame;
private editorResize$: ResizeObserver;
private ignoreChange = false;
@ -136,7 +137,7 @@ export class JsFuncComponent implements OnInit, OnDestroy, ControlValueAccessor,
});
}
const editorElement = this.javascriptEditorElmRef.nativeElement;
let editorOptions: Partial<ace.Ace.EditorOptions> = {
let editorOptions: Partial<Ace.EditorOptions> = {
mode: 'ace/mode/javascript',
showGutter: true,
showPrintMargin: true,
@ -150,22 +151,27 @@ export class JsFuncComponent implements OnInit, OnDestroy, ControlValueAccessor,
};
editorOptions = {...editorOptions, ...advancedOptions};
this.jsEditor = ace.edit(editorElement, editorOptions);
this.jsEditor.session.setUseWrapMode(true);
this.jsEditor.setValue(this.modelValue ? this.modelValue : '', -1);
this.jsEditor.on('change', () => {
if (!this.ignoreChange) {
this.cleanupJsErrors();
this.updateView();
getAce().subscribe(
(ace) => {
this.jsEditor = ace.edit(editorElement, editorOptions);
this.jsEditor.session.setUseWrapMode(true);
this.jsEditor.setValue(this.modelValue ? this.modelValue : '', -1);
this.jsEditor.setReadOnly(this.disabled);
this.jsEditor.on('change', () => {
if (!this.ignoreChange) {
this.cleanupJsErrors();
this.updateView();
}
});
if (this.editorCompleter) {
this.jsEditor.completers = [this.editorCompleter, ...(this.jsEditor.completers || [])];
}
this.editorResize$ = new ResizeObserver(() => {
this.onAceEditorResize();
});
this.editorResize$.observe(editorElement);
}
});
if (this.editorCompleter) {
this.jsEditor.completers = [this.editorCompleter, ...(this.jsEditor.completers || [])];
}
this.editorResize$ = new ResizeObserver(() => {
this.onAceEditorResize();
});
this.editorResize$.observe(editorElement);
);
}
ngOnDestroy(): void {
@ -294,11 +300,11 @@ export class JsFuncComponent implements OnInit, OnDestroy, ControlValueAccessor,
if (details.columnNumber) {
column = details.columnNumber;
}
const errorMarkerId = this.jsEditor.session.addMarker(new ace.Range(line, 0, line, Infinity),
const errorMarkerId = this.jsEditor.session.addMarker(new Range(line, 0, line, Infinity),
'ace_active-line', 'screenLine');
this.errorMarkers.push(errorMarkerId);
const annotations = this.jsEditor.session.getAnnotations();
const errorAnnotation: ace.Ace.Annotation = {
const errorAnnotation: Ace.Annotation = {
row: line,
column,
text: details.message,

View File

@ -26,7 +26,7 @@ import {
ViewChild
} from '@angular/core';
import { ControlValueAccessor, FormControl, NG_VALIDATORS, NG_VALUE_ACCESSOR, Validator } from '@angular/forms';
import * as ace from 'ace-builds';
import { Ace } from 'ace-builds';
import { coerceBooleanProperty } from '@angular/cdk/coercion';
import { ActionNotificationHide, ActionNotificationShow } from '@core/notification/notification.actions';
import { Store } from '@ngrx/store';
@ -35,6 +35,7 @@ import { ContentType, contentTypesMap } from '@shared/models/constants';
import { CancelAnimationFrame, RafService } from '@core/services/raf.service';
import { guid } from '@core/utils';
import { ResizeObserver } from '@juggle/resize-observer';
import { getAce } from '@shared/models/ace/ace.models';
@Component({
selector: 'tb-json-content',
@ -58,7 +59,7 @@ export class JsonContentComponent implements OnInit, ControlValueAccessor, Valid
@ViewChild('jsonEditor', {static: true})
jsonEditorElmRef: ElementRef;
private jsonEditor: ace.Ace.Editor;
private jsonEditor: Ace.Editor;
private editorsResizeCaf: CancelAnimationFrame;
private editorResize$: ResizeObserver;
private ignoreChange = false;
@ -123,7 +124,7 @@ export class JsonContentComponent implements OnInit, ControlValueAccessor, Valid
if (this.contentType) {
mode = contentTypesMap.get(this.contentType).code;
}
let editorOptions: Partial<ace.Ace.EditorOptions> = {
let editorOptions: Partial<Ace.EditorOptions> = {
mode: `ace/mode/${mode}`,
showGutter: true,
showPrintMargin: false,
@ -137,22 +138,27 @@ export class JsonContentComponent implements OnInit, ControlValueAccessor, Valid
};
editorOptions = {...editorOptions, ...advancedOptions};
this.jsonEditor = ace.edit(editorElement, editorOptions);
this.jsonEditor.session.setUseWrapMode(true);
this.jsonEditor.setValue(this.contentBody ? this.contentBody : '', -1);
this.jsonEditor.on('change', () => {
if (!this.ignoreChange) {
this.cleanupJsonErrors();
this.updateView();
getAce().subscribe(
(ace) => {
this.jsonEditor = ace.edit(editorElement, editorOptions);
this.jsonEditor.session.setUseWrapMode(true);
this.jsonEditor.setValue(this.contentBody ? this.contentBody : '', -1);
this.jsonEditor.setReadOnly(this.disabled || this.readonly);
this.jsonEditor.on('change', () => {
if (!this.ignoreChange) {
this.cleanupJsonErrors();
this.updateView();
}
});
this.jsonEditor.on('blur', () => {
this.contentValid = !this.validateContent || this.doValidate(true);
});
this.editorResize$ = new ResizeObserver(() => {
this.onAceEditorResize();
});
this.editorResize$.observe(editorElement);
}
});
this.jsonEditor.on('blur', () => {
this.contentValid = !this.validateContent || this.doValidate(true);
});
this.editorResize$ = new ResizeObserver(() => {
this.onAceEditorResize();
});
this.editorResize$.observe(editorElement);
);
}
ngOnDestroy(): void {

View File

@ -16,10 +16,20 @@
import * as React from 'react';
import ThingsboardBaseComponent from './json-form-base-component';
import reactCSS from 'reactcss';
import ReactAce from 'react-ace';
import Button from '@material-ui/core/Button';
import { JsonFormFieldProps, JsonFormFieldState } from '@shared/components/json-form/react/json-form.models';
import { IEditorProps } from 'react-ace/src/types';
import { map, mergeMap } from 'rxjs/operators';
import { loadAceDependencies } from '@shared/models/ace/ace.models';
import { from } from 'rxjs';
const ReactAce = React.lazy(() => {
return loadAceDependencies().pipe(
mergeMap(() => {
return from(import('react-ace'));
})
).toPromise();
});
interface ThingsboardAceEditorProps extends JsonFormFieldProps {
mode: string;
@ -153,22 +163,24 @@ class ThingsboardAceEditor extends React.Component<ThingsboardAceEditorProps, Th
'Exit fullscreen' : 'Fullscreen'}
</Button>
</div>
<ReactAce mode={this.props.mode}
height={this.state.isFull ? '100%' : '150px'}
width={this.state.isFull ? '100%' : '300px'}
theme='github'
onChange={this.onValueChanged}
onFocus={this.onFocus}
onBlur={this.onBlur}
onLoad={this.onLoad}
name={this.props.form.title}
value={this.state.value}
readOnly={this.props.form.readonly}
editorProps={{$blockScrolling: Infinity}}
enableBasicAutocompletion={true}
enableSnippets={true}
enableLiveAutocompletion={true}
style={style}/>
<React.Suspense fallback={<div>Loading...</div>}>
<ReactAce mode={this.props.mode}
height={this.state.isFull ? '100%' : '150px'}
width={this.state.isFull ? '100%' : '300px'}
theme='github'
onChange={this.onValueChanged}
onFocus={this.onFocus}
onBlur={this.onBlur}
onLoad={this.onLoad}
name={this.props.form.title}
value={this.state.value}
readOnly={this.props.form.readonly}
editorProps={{$blockScrolling: Infinity}}
enableBasicAutocompletion={true}
enableSnippets={true}
enableLiveAutocompletion={true}
style={style}/>
</React.Suspense>
</div>
<div className='json-form-error'
style={{opacity: this.props.valid ? '0' : '1'}}>{this.props.error}</div>

View File

@ -16,7 +16,7 @@
import { Component, ElementRef, forwardRef, Input, OnDestroy, OnInit, ViewChild } from '@angular/core';
import { ControlValueAccessor, FormControl, NG_VALIDATORS, NG_VALUE_ACCESSOR, Validator } from '@angular/forms';
import * as ace from 'ace-builds';
import { Ace } from 'ace-builds';
import { coerceBooleanProperty } from '@angular/cdk/coercion';
import { ActionNotificationHide, ActionNotificationShow } from '@core/notification/notification.actions';
import { Store } from '@ngrx/store';
@ -24,6 +24,7 @@ import { AppState } from '@core/core.state';
import { CancelAnimationFrame, RafService } from '@core/services/raf.service';
import { guid } from '@core/utils';
import { ResizeObserver } from '@juggle/resize-observer';
import { getAce } from '@shared/models/ace/ace.models';
@Component({
selector: 'tb-json-object-edit',
@ -47,7 +48,7 @@ export class JsonObjectEditComponent implements OnInit, ControlValueAccessor, Va
@ViewChild('jsonEditor', {static: true})
jsonEditorElmRef: ElementRef;
private jsonEditor: ace.Ace.Editor;
private jsonEditor: Ace.Editor;
private editorsResizeCaf: CancelAnimationFrame;
private editorResize$: ResizeObserver;
@ -102,7 +103,7 @@ export class JsonObjectEditComponent implements OnInit, ControlValueAccessor, Va
ngOnInit(): void {
const editorElement = this.jsonEditorElmRef.nativeElement;
let editorOptions: Partial<ace.Ace.EditorOptions> = {
let editorOptions: Partial<Ace.EditorOptions> = {
mode: 'ace/mode/json',
showGutter: true,
showPrintMargin: false,
@ -116,19 +117,24 @@ export class JsonObjectEditComponent implements OnInit, ControlValueAccessor, Va
};
editorOptions = {...editorOptions, ...advancedOptions};
this.jsonEditor = ace.edit(editorElement, editorOptions);
this.jsonEditor.session.setUseWrapMode(false);
this.jsonEditor.setValue(this.contentValue ? this.contentValue : '', -1);
this.jsonEditor.on('change', () => {
if (!this.ignoreChange) {
this.cleanupJsonErrors();
this.updateView();
getAce().subscribe(
(ace) => {
this.jsonEditor = ace.edit(editorElement, editorOptions);
this.jsonEditor.session.setUseWrapMode(false);
this.jsonEditor.setValue(this.contentValue ? this.contentValue : '', -1);
this.jsonEditor.setReadOnly(this.disabled || this.readonly);
this.jsonEditor.on('change', () => {
if (!this.ignoreChange) {
this.cleanupJsonErrors();
this.updateView();
}
});
this.editorResize$ = new ResizeObserver(() => {
this.onAceEditorResize();
});
this.editorResize$.observe(editorElement);
}
});
this.editorResize$ = new ResizeObserver(() => {
this.onAceEditorResize();
});
this.editorResize$.observe(editorElement);
);
}
ngOnDestroy(): void {

View File

@ -0,0 +1,327 @@
///
/// Copyright © 2016-2020 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 { Ace } from 'ace-builds';
import { Observable } from 'rxjs/internal/Observable';
import { forkJoin, from, of } from 'rxjs';
import { map, mergeMap, tap } from 'rxjs/operators';
let aceDependenciesLoaded = false;
let aceModule: any;
export function loadAceDependencies(): Observable<any> {
if (aceDependenciesLoaded) {
return of(null);
} else {
const aceObservables: Observable<any>[] = [];
aceObservables.push(from(import('ace-builds/src-noconflict/ext-language_tools')));
aceObservables.push(from(import('ace-builds/src-noconflict/ext-searchbox')));
aceObservables.push(from(import('ace-builds/src-noconflict/mode-java')));
aceObservables.push(from(import('ace-builds/src-noconflict/mode-css')));
aceObservables.push(from(import('ace-builds/src-noconflict/mode-json')));
aceObservables.push(from(import('ace-builds/src-noconflict/mode-javascript')));
aceObservables.push(from(import('ace-builds/src-noconflict/mode-text')));
aceObservables.push(from(import('ace-builds/src-noconflict/mode-markdown')));
aceObservables.push(from(import('ace-builds/src-noconflict/mode-html')));
aceObservables.push(from(import('ace-builds/src-noconflict/snippets/java')));
aceObservables.push(from(import('ace-builds/src-noconflict/snippets/css')));
aceObservables.push(from(import('ace-builds/src-noconflict/snippets/json')));
aceObservables.push(from(import('ace-builds/src-noconflict/snippets/javascript')));
aceObservables.push(from(import('ace-builds/src-noconflict/snippets/text')));
aceObservables.push(from(import('ace-builds/src-noconflict/snippets/markdown')));
aceObservables.push(from(import('ace-builds/src-noconflict/snippets/html')));
aceObservables.push(from(import('ace-builds/src-noconflict/theme-github')));
return forkJoin(aceObservables).pipe(
tap(() => {
aceDependenciesLoaded = true;
})
);
}
}
export function getAce(): Observable<any> {
if (aceModule) {
return of(aceModule);
} else {
return from(import('ace')).pipe(
mergeMap((module) => {
return loadAceDependencies().pipe(
map(() => module)
);
}),
tap((module) => {
aceModule = module;
})
);
}
}
export class Range implements Ace.Range {
public start: Ace.Point;
public end: Ace.Point;
constructor(startRow: number, startColumn: number, endRow: number, endColumn: number) {
this.start = {
row: startRow,
column: startColumn
};
this.end = {
row: endRow,
column: endColumn
};
}
static fromPoints(start: Ace.Point, end: Ace.Point): Ace.Range {
return new Range(start.row, start.column, end.row, end.column);
}
clipRows(firstRow: number, lastRow: number): Ace.Range {
let end: Ace.Point;
let start: Ace.Point;
if (this.end.row > lastRow) {
end = {row: lastRow + 1, column: 0};
} else if (this.end.row < firstRow) {
end = {row: firstRow, column: 0};
}
if (this.start.row > lastRow) {
start = {row: lastRow + 1, column: 0};
} else if (this.start.row < firstRow) {
start = {row: firstRow, column: 0};
}
return Range.fromPoints(start || this.start, end || this.end);
}
clone(): Ace.Range {
return Range.fromPoints(this.start, this.end);
}
collapseRows(): Ace.Range {
if (this.end.column === 0) {
return new Range(this.start.row, 0, Math.max(this.start.row, this.end.row - 1), 0);
} else {
return new Range(this.start.row, 0, this.end.row, 0);
}
}
compare(row: number, column: number): number {
if (!this.isMultiLine()) {
if (row === this.start.row) {
return column < this.start.column ? -1 : (column > this.end.column ? 1 : 0);
}
}
if (row < this.start.row) {
return -1;
}
if (row > this.end.row) {
return 1;
}
if (this.start.row === row) {
return column >= this.start.column ? 0 : -1;
}
if (this.end.row === row) {
return column <= this.end.column ? 0 : 1;
}
return 0;
}
compareEnd(row: number, column: number): number {
if (this.end.row === row && this.end.column === column) {
return 1;
} else {
return this.compare(row, column);
}
}
compareInside(row: number, column: number): number {
if (this.end.row === row && this.end.column === column) {
return 1;
} else if (this.start.row === row && this.start.column === column) {
return -1;
} else {
return this.compare(row, column);
}
}
comparePoint(p: Ace.Point): number {
return this.compare(p.row, p.column);
}
compareRange(range: Ace.Range): number {
let cmp: number;
const end = range.end;
const start = range.start;
cmp = this.compare(end.row, end.column);
if (cmp === 1) {
cmp = this.compare(start.row, start.column);
if (cmp === 1) {
return 2;
} else if (cmp === 0) {
return 1;
} else {
return 0;
}
} else if (cmp === -1) {
return -2;
} else {
cmp = this.compare(start.row, start.column);
if (cmp === -1) {
return -1;
} else if (cmp === 1) {
return 42;
} else {
return 0;
}
}
}
compareStart(row: number, column: number): number {
if (this.start.row === row && this.start.column === column) {
return -1;
} else {
return this.compare(row, column);
}
}
contains(row: number, column: number): boolean {
return this.compare(row, column) === 0;
}
containsRange(range: Ace.Range): boolean {
return this.comparePoint(range.start) === 0 && this.comparePoint(range.end) === 0;
}
extend(row: number, column: number): Ace.Range {
const cmp = this.compare(row, column);
let end: Ace.Point;
let start: Ace.Point;
if (cmp === 0) {
return this;
} else if (cmp === -1) {
start = {row, column};
} else {
end = {row, column};
}
return Range.fromPoints(start || this.start, end || this.end);
}
inside(row: number, column: number): boolean {
if (this.compare(row, column) === 0) {
if (this.isEnd(row, column) || this.isStart(row, column)) {
return false;
} else {
return true;
}
}
return false;
}
insideEnd(row: number, column: number): boolean {
if (this.compare(row, column) === 0) {
if (this.isStart(row, column)) {
return false;
} else {
return true;
}
}
return false;
}
insideStart(row: number, column: number): boolean {
if (this.compare(row, column) === 0) {
if (this.isEnd(row, column)) {
return false;
} else {
return true;
}
}
return false;
}
intersects(range: Ace.Range): boolean {
const cmp = this.compareRange(range);
return (cmp === -1 || cmp === 0 || cmp === 1);
}
isEmpty(): boolean {
return (this.start.row === this.end.row && this.start.column === this.end.column);
}
isEnd(row: number, column: number): boolean {
return this.end.row === row && this.end.column === column;
}
isEqual(range: Ace.Range): boolean {
return this.start.row === range.start.row &&
this.end.row === range.end.row &&
this.start.column === range.start.column &&
this.end.column === range.end.column;
}
isMultiLine(): boolean {
return (this.start.row !== this.end.row);
}
isStart(row: number, column: number): boolean {
return this.start.row === row && this.start.column === column;
}
moveBy(row: number, column: number): void {
this.start.row += row;
this.start.column += column;
this.end.row += row;
this.end.column += column;
}
setEnd(row: number, column: number): void {
if (typeof row === 'object') {
this.end.column = (row as Ace.Point).column;
this.end.row = (row as Ace.Point).row;
} else {
this.end.row = row;
this.end.column = column;
}
}
setStart(row: number, column: number): void {
if (typeof row === 'object') {
this.start.column = (row as Ace.Point).column;
this.start.row = (row as Ace.Point).row;
} else {
this.start.row = row;
this.start.column = column;
}
}
toScreenRange(session: Ace.EditSession): Ace.Range {
const screenPosStart = session.documentToScreenPosition(this.start);
const screenPosEnd = session.documentToScreenPosition(this.end);
return new Range(
screenPosStart.row, screenPosStart.column,
screenPosEnd.row, screenPosEnd.column
);
}
}

View File

@ -14,7 +14,7 @@
/// limitations under the License.
///
import * as ace from 'ace-builds';
import { Ace } from 'ace-builds';
export type tbMetaType = 'object' | 'function' | 'service' | 'property' | 'argument';
@ -41,7 +41,7 @@ export interface TbEditorCompletion {
children?: TbEditorCompletions;
}
interface TbEditorAceCompletion extends ace.Ace.Completion {
interface TbEditorAceCompletion extends Ace.Completion {
isTbEditorAceCompletion: true;
snippet: string;
description?: string;
@ -50,7 +50,7 @@ interface TbEditorAceCompletion extends ace.Ace.Completion {
return?: FunctionArgType;
}
export class TbEditorCompleter implements ace.Ace.Completer {
export class TbEditorCompleter implements Ace.Completer {
identifierRegexps: RegExp[] = [
/[a-zA-Z_0-9\$\-\u00A2-\u2000\u2070-\uFFFF.]/
@ -59,8 +59,8 @@ export class TbEditorCompleter implements ace.Ace.Completer {
constructor(private editorCompletions: TbEditorCompletions) {
}
getCompletions(editor: ace.Ace.Editor, session: ace.Ace.EditSession,
position: ace.Ace.Point, prefix: string, callback: ace.Ace.CompleterCallback): void {
getCompletions(editor: Ace.Editor, session: Ace.EditSession,
position: Ace.Point, prefix: string, callback: Ace.CompleterCallback): void {
const result = this.prepareCompletions(prefix);
if (result) {
callback(null, result);
@ -91,7 +91,7 @@ export class TbEditorCompleter implements ace.Ace.Completer {
return parts;
}
private prepareCompletions(prefix: string): ace.Ace.Completion[] {
private prepareCompletions(prefix: string): Ace.Completion[] {
const path = this.resolvePath(prefix);
if (path !== null) {
return this.toAceCompletionsList(this.editorCompletions, path);
@ -100,8 +100,8 @@ export class TbEditorCompleter implements ace.Ace.Completer {
}
}
private toAceCompletionsList(completions: TbEditorCompletions, parentPath: string[]): ace.Ace.Completion[] {
const result: ace.Ace.Completion[] = [];
private toAceCompletionsList(completions: TbEditorCompletions, parentPath: string[]): Ace.Completion[] {
const result: Ace.Completion[] = [];
let targetCompletions = completions;
let parentPrefix = '';
if (parentPath.length) {
@ -116,7 +116,7 @@ export class TbEditorCompleter implements ace.Ace.Completer {
return result;
}
private toAceCompletion(name: string, completion: TbEditorCompletion, parentPrefix: string): ace.Ace.Completion {
private toAceCompletion(name: string, completion: TbEditorCompletion, parentPrefix: string): Ace.Completion {
const aceCompletion: TbEditorAceCompletion = {
isTbEditorAceCompletion: true,
snippet: parentPrefix + name,
@ -175,7 +175,7 @@ export class TbEditorCompleter implements ace.Ace.Completer {
if (completion.args || completion.return) {
let functionInfoBlock = '<div class="tb-function-info">';
if (completion.args) {
functionInfoBlock += '<div class="tb-api-title">Parameters</div>'
functionInfoBlock += '<div class="tb-api-title">Parameters</div>';
let argsTable = '<table class="tb-api-table"><tbody>';
const strArgs: string[] = [];
for (const arg of completion.args) {
@ -185,7 +185,7 @@ export class TbEditorCompleter implements ace.Ace.Completer {
}
strArg += '</code></td><td>';
if (arg.type) {
strArg += `<code>${arg.type}</code>`
strArg += `<code>${arg.type}</code>`;
}
strArg += '</td><td class="arg-description">';
if (arg.description) {

View File

@ -75,7 +75,6 @@
import './zone-flags';
import 'zone.js/dist/zone'; // Included with Angular CLI.
import 'core-js/es/array';
import moment from 'moment';
/***************************************************************************************************
* APPLICATION IMPORTS
@ -87,26 +86,4 @@ import moment from 'moment';
* WIDGETS IMPORTS
*/
import cssjs from '@core/css/css';
import { TbFlot } from '@home/components/widget/lib/flot-widget';
import { TbAnalogueCompass } from '@home/components/widget/lib/analogue-compass';
import { TbAnalogueRadialGauge } from '@home/components/widget/lib/analogue-radial-gauge';
import { TbAnalogueLinearGauge } from '@home/components/widget/lib/analogue-linear-gauge';
import { TbCanvasDigitalGauge } from '@home/components/widget/lib/digital-gauge';
import { TbMapWidgetV2 } from '@home/components/widget/lib/maps/map-widget2';
import { TbTripAnimationWidget } from '@app/modules/home/components/widget/trip-animation/trip-animation.component';
import * as tinycolor_ from 'tinycolor2';
const tinycolor = tinycolor_;
(window as any).tinycolor = tinycolor;
(window as any).cssjs = cssjs;
(window as any).moment = moment;
(window as any).TbFlot = TbFlot;
(window as any).TbAnalogueCompass = TbAnalogueCompass;
(window as any).TbAnalogueRadialGauge = TbAnalogueRadialGauge;
(window as any).TbAnalogueLinearGauge = TbAnalogueLinearGauge;
(window as any).TbCanvasDigitalGauge = TbCanvasDigitalGauge;
(window as any).TbMapWidgetV2 = TbMapWidgetV2;
(window as any).TbTripAnimationWidget = TbTripAnimationWidget;
(window as any).GAUGES_NO_AUTO_INIT = true;

View File

@ -14,7 +14,7 @@
/// limitations under the License.
///
import * as L from 'leaflet'
import * as L from 'leaflet';
declare module 'leaflet' {

View File

@ -34,6 +34,9 @@
"@home/*": ["src/app/modules/home/*"],
"jszip": [
"node_modules/jszip/dist/jszip.min.js"
],
"ace": [
"node_modules/ace-builds/src-noconflict/ace.js"
]
},
"lib": [