UI: Optimizations
This commit is contained in:
parent
0ae74b77e3
commit
1ff3eeaf93
@ -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": {
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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(
|
||||
|
||||
@ -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];
|
||||
|
||||
@ -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;
|
||||
|
||||
|
||||
@ -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>
|
||||
`);
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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();
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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>
|
||||
`);
|
||||
}
|
||||
|
||||
@ -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';
|
||||
|
||||
@ -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';
|
||||
|
||||
|
||||
@ -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 {
|
||||
|
||||
|
||||
@ -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';
|
||||
|
||||
@ -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
|
||||
};
|
||||
|
||||
@ -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'
|
||||
}
|
||||
};
|
||||
|
||||
@ -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();
|
||||
|
||||
@ -546,7 +546,7 @@ class TimeseriesDatasource implements DataSource<TimeseriesRow> {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
rows = Object.values(rowsMap);
|
||||
rows = Object.keys(rowsMap).map(itm => rowsMap[itm]);
|
||||
}
|
||||
return rows;
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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 {
|
||||
|
||||
327
ui-ngx/src/app/shared/models/ace/ace.models.ts
Normal file
327
ui-ngx/src/app/shared/models/ace/ace.models.ts
Normal 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
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
@ -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) {
|
||||
|
||||
@ -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;
|
||||
|
||||
2
ui-ngx/src/typings/add-marker.d.ts
vendored
2
ui-ngx/src/typings/add-marker.d.ts
vendored
@ -14,7 +14,7 @@
|
||||
/// limitations under the License.
|
||||
///
|
||||
|
||||
import * as L from 'leaflet'
|
||||
import * as L from 'leaflet';
|
||||
|
||||
declare module 'leaflet' {
|
||||
|
||||
|
||||
@ -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": [
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user