Commit 1ff3eeaf93a37943c5ac17f7ebf4bdb5fddbedd1

Authored by Igor Kulikov
1 parent 0ae74b77

UI: Optimizations

Showing 33 changed files with 1293 additions and 798 deletions
... ... @@ -27,22 +27,22 @@
27 27 "src/assets",
28 28 {
29 29 "glob": "worker-html.js",
30   - "input": "./node_modules/ace-builds/src-min/",
  30 + "input": "./node_modules/ace-builds/src-noconflict/",
31 31 "output": "/"
32 32 },
33 33 {
34 34 "glob": "worker-css.js",
35   - "input": "./node_modules/ace-builds/src-min/",
  35 + "input": "./node_modules/ace-builds/src-noconflict/",
36 36 "output": "/"
37 37 },
38 38 {
39 39 "glob": "worker-json.js",
40   - "input": "./node_modules/ace-builds/src-min/",
  40 + "input": "./node_modules/ace-builds/src-noconflict/",
41 41 "output": "/"
42 42 },
43 43 {
44 44 "glob": "worker-javascript.js",
45   - "input": "./node_modules/ace-builds/src-min/",
  45 + "input": "./node_modules/ace-builds/src-noconflict/",
46 46 "output": "/"
47 47 },
48 48 {
... ... @@ -102,24 +102,6 @@
102 102 "node_modules/js-beautify/js/lib/beautify.js",
103 103 "node_modules/js-beautify/js/lib/beautify-css.js",
104 104 "node_modules/js-beautify/js/lib/beautify-html.js",
105   - "node_modules/ace-builds/src-min/ace.js",
106   - "node_modules/ace-builds/src-min/ext-language_tools.js",
107   - "node_modules/ace-builds/src-min/ext-searchbox.js",
108   - "node_modules/ace-builds/src-min/theme-github.js",
109   - "node_modules/ace-builds/src-min/mode-text.js",
110   - "node_modules/ace-builds/src-min/mode-markdown.js",
111   - "node_modules/ace-builds/src-min/mode-html.js",
112   - "node_modules/ace-builds/src-min/mode-css.js",
113   - "node_modules/ace-builds/src-min/mode-json.js",
114   - "node_modules/ace-builds/src-min/mode-java.js",
115   - "node_modules/ace-builds/src-min/mode-javascript.js",
116   - "node_modules/ace-builds/src-min/snippets/text.js",
117   - "node_modules/ace-builds/src-min/snippets/markdown.js",
118   - "node_modules/ace-builds/src-min/snippets/html.js",
119   - "node_modules/ace-builds/src-min/snippets/css.js",
120   - "node_modules/ace-builds/src-min/snippets/json.js",
121   - "node_modules/ace-builds/src-min/snippets/java.js",
122   - "node_modules/ace-builds/src-min/snippets/javascript.js",
123 105 "node_modules/systemjs/dist/system.js",
124 106 "node_modules/jstree/dist/jstree.min.js"
125 107 ],
... ... @@ -143,7 +125,11 @@
143 125 "hoist-non-react-statics",
144 126 "classnames",
145 127 "raf",
146   - "moment-timezone"
  128 + "moment-timezone",
  129 + "tinycolor2",
  130 + "json-schema-defaults",
  131 + "leaflet-providers",
  132 + "lodash"
147 133 ]
148 134 },
149 135 "configurations": {
... ...
... ... @@ -69,16 +69,19 @@ export function addCondition(schema: JsonSettingsSchema, condition: string, excl
69 69 return {
70 70 key: element,
71 71 condition
72   - }
  72 + };
73 73 }
74 74 if (typeof element === 'object') {
75 75 if (element.condition) {
76   - element.condition += ' && ' + condition
  76 + element.condition += ' && ' + condition;
  77 + }
  78 + else {
  79 + element.condition = condition;
77 80 }
78   - else element.condition = condition;
79 81 }
80 82 }
81 83 return element;
82 84 });
83 85 return schema;
84 86 }
  87 +
... ...
... ... @@ -19,10 +19,10 @@ import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
19 19 import { Store } from '@ngrx/store';
20 20 import { AppState } from '@core/core.state';
21 21 import { ActionStatus, AuditLog } from '@shared/models/audit-log.models';
22   -
23   -import * as ace from 'ace-builds';
  22 +import { Ace } from 'ace-builds';
24 23 import { DialogComponent } from '@shared/components/dialog.component';
25 24 import { Router } from '@angular/router';
  25 +import { getAce } from '@shared/models/ace/ace.models';
26 26
27 27 export interface AuditLogDetailsDialogData {
28 28 auditLog: AuditLog;
... ... @@ -37,11 +37,9 @@ export class AuditLogDetailsDialogComponent extends DialogComponent<AuditLogDeta
37 37
38 38 @ViewChild('actionDataEditor', {static: true})
39 39 actionDataEditorElmRef: ElementRef;
40   - private actionDataEditor: ace.Ace.Editor;
41 40
42 41 @ViewChild('failureDetailsEditor', {static: true})
43 42 failureDetailsEditorElmRef: ElementRef;
44   - private failureDetailsEditor: ace.Ace.Editor;
45 43
46 44 auditLog: AuditLog;
47 45 displayFailureDetails: boolean;
... ... @@ -62,15 +60,15 @@ export class AuditLogDetailsDialogComponent extends DialogComponent<AuditLogDeta
62 60 this.actionData = this.auditLog.actionData ? JSON.stringify(this.auditLog.actionData, null, 2) : '';
63 61 this.actionFailureDetails = this.auditLog.actionFailureDetails;
64 62
65   - this.actionDataEditor = this.createEditor(this.actionDataEditorElmRef, this.actionData);
  63 + this.createEditor(this.actionDataEditorElmRef, this.actionData);
66 64 if (this.displayFailureDetails) {
67   - this.failureDetailsEditor = this.createEditor(this.failureDetailsEditorElmRef, this.actionFailureDetails);
  65 + this.createEditor(this.failureDetailsEditorElmRef, this.actionFailureDetails);
68 66 }
69 67 }
70 68
71   - createEditor(editorElementRef: ElementRef, content: string): ace.Ace.Editor {
  69 + createEditor(editorElementRef: ElementRef, content: string): void {
72 70 const editorElement = editorElementRef.nativeElement;
73   - let editorOptions: Partial<ace.Ace.EditorOptions> = {
  71 + let editorOptions: Partial<Ace.EditorOptions> = {
74 72 mode: 'ace/mode/java',
75 73 theme: 'ace/theme/github',
76 74 showGutter: false,
... ... @@ -85,14 +83,17 @@ export class AuditLogDetailsDialogComponent extends DialogComponent<AuditLogDeta
85 83 };
86 84
87 85 editorOptions = {...editorOptions, ...advancedOptions};
88   - const editor = ace.edit(editorElement, editorOptions);
89   - editor.session.setUseWrapMode(false);
90   - editor.setValue(content, -1);
91   - this.updateEditorSize(editorElement, content, editor);
92   - return editor;
  86 + getAce().subscribe(
  87 + (ace) => {
  88 + const editor = ace.edit(editorElement, editorOptions);
  89 + editor.session.setUseWrapMode(false);
  90 + editor.setValue(content, -1);
  91 + this.updateEditorSize(editorElement, content, editor);
  92 + }
  93 + );
93 94 }
94 95
95   - updateEditorSize(editorElement: any, content: string, editor: ace.Ace.Editor) {
  96 + updateEditorSize(editorElement: any, content: string, editor: Ace.Editor) {
96 97 let newHeight = 200;
97 98 let newWidth = 600;
98 99 if (content && content.length > 0) {
... ...
... ... @@ -19,10 +19,11 @@ import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
19 19 import { Store } from '@ngrx/store';
20 20 import { AppState } from '@core/core.state';
21 21
22   -import * as ace from 'ace-builds';
  22 +import { Ace } from 'ace-builds';
23 23 import { DialogComponent } from '@shared/components/dialog.component';
24 24 import { Router } from '@angular/router';
25 25 import { ContentType, contentTypesMap } from '@shared/models/constants';
  26 +import { getAce } from '@shared/models/ace/ace.models';
26 27
27 28 export interface EventContentDialogData {
28 29 content: string;
... ... @@ -39,7 +40,6 @@ export class EventContentDialogComponent extends DialogComponent<EventContentDia
39 40
40 41 @ViewChild('eventContentEditor', {static: true})
41 42 eventContentEditorElmRef: ElementRef;
42   - private eventContentEditor: ace.Ace.Editor;
43 43
44 44 content: string;
45 45 title: string;
... ... @@ -58,10 +58,10 @@ export class EventContentDialogComponent extends DialogComponent<EventContentDia
58 58 this.title = this.data.title;
59 59 this.contentType = this.data.contentType;
60 60
61   - this.eventContentEditor = this.createEditor(this.eventContentEditorElmRef, this.content);
  61 + this.createEditor(this.eventContentEditorElmRef, this.content);
62 62 }
63 63
64   - createEditor(editorElementRef: ElementRef, content: string): ace.Ace.Editor {
  64 + createEditor(editorElementRef: ElementRef, content: string) {
65 65 const editorElement = editorElementRef.nativeElement;
66 66 let mode = 'java';
67 67 if (this.contentType) {
... ... @@ -70,7 +70,7 @@ export class EventContentDialogComponent extends DialogComponent<EventContentDia
70 70 content = js_beautify(content, {indent_size: 4});
71 71 }
72 72 }
73   - let editorOptions: Partial<ace.Ace.EditorOptions> = {
  73 + let editorOptions: Partial<Ace.EditorOptions> = {
74 74 mode: `ace/mode/${mode}`,
75 75 theme: 'ace/theme/github',
76 76 showGutter: false,
... ... @@ -85,14 +85,17 @@ export class EventContentDialogComponent extends DialogComponent<EventContentDia
85 85 };
86 86
87 87 editorOptions = {...editorOptions, ...advancedOptions};
88   - const editor = ace.edit(editorElement, editorOptions);
89   - editor.session.setUseWrapMode(false);
90   - editor.setValue(content, -1);
91   - this.updateEditorSize(editorElement, content, editor);
92   - return editor;
  88 + getAce().subscribe(
  89 + (ace) => {
  90 + const editor = ace.edit(editorElement, editorOptions);
  91 + editor.session.setUseWrapMode(false);
  92 + editor.setValue(content, -1);
  93 + this.updateEditorSize(editorElement, content, editor);
  94 + }
  95 + );
93 96 }
94 97
95   - updateEditorSize(editorElement: any, content: string, editor: ace.Ace.Editor) {
  98 + updateEditorSize(editorElement: any, content: string, editor: Ace.Editor) {
96 99 let newHeight = 400;
97 100 let newWidth = 600;
98 101 if (content && content.length > 0) {
... ...
... ... @@ -220,7 +220,7 @@ export class KeyFilterDialogComponent extends
220 220 let keyNameObservable: Observable<Array<string>>;
221 221 switch (this.keyFilterFormGroup.get('key.type').value) {
222 222 case EntityKeyType.ENTITY_FIELD:
223   - keyNameObservable = of(Object.values(entityFields).map(entityField => entityField.keyName).sort());
  223 + keyNameObservable = of(Object.keys(entityFields).map(itm => entityFields[itm]).map(entityField => entityField.keyName).sort());
224 224 break;
225 225 case EntityKeyType.ATTRIBUTE:
226 226 keyNameObservable = this.deviceProfileService.getDeviceProfileDevicesAttributesKeys(
... ...
... ... @@ -32,11 +32,15 @@ import { PageComponent } from '@shared/components/page.component';
32 32 import { Store } from '@ngrx/store';
33 33 import { AppState } from '@core/core.state';
34 34 import { CustomActionDescriptor } from '@shared/models/widget.models';
35   -import * as ace from 'ace-builds';
  35 +import { Ace } from 'ace-builds';
36 36 import { CancelAnimationFrame, RafService } from '@core/services/raf.service';
37 37 import { css_beautify, html_beautify } from 'js-beautify';
38 38 import { ResizeObserver } from '@juggle/resize-observer';
39 39 import { CustomPrettyActionEditorCompleter } from '@home/components/widget/action/custom-action.models';
  40 +import { Observable } from 'rxjs/internal/Observable';
  41 +import { forkJoin, from } from 'rxjs';
  42 +import { map, tap } from 'rxjs/operators';
  43 +import { getAce } from '@shared/models/ace/ace.models';
40 44
41 45 @Component({
42 46 selector: 'tb-custom-action-pretty-resources-tabs',
... ... @@ -64,11 +68,11 @@ export class CustomActionPrettyResourcesTabsComponent extends PageComponent impl
64 68 htmlFullscreen = false;
65 69 cssFullscreen = false;
66 70
67   - aceEditors: ace.Ace.Editor[] = [];
  71 + aceEditors: Ace.Editor[] = [];
68 72 editorsResizeCafs: {[editorId: string]: CancelAnimationFrame} = {};
69 73 aceResize$: ResizeObserver;
70   - htmlEditor: ace.Ace.Editor;
71   - cssEditor: ace.Ace.Editor;
  74 + htmlEditor: Ace.Editor;
  75 + cssEditor: Ace.Editor;
72 76 setValuesPending = false;
73 77
74 78 customPrettyActionEditorCompleter = CustomPrettyActionEditorCompleter;
... ... @@ -80,11 +84,12 @@ export class CustomActionPrettyResourcesTabsComponent extends PageComponent impl
80 84 }
81 85
82 86 ngOnInit(): void {
83   - this.initAceEditors();
84   - if (this.setValuesPending) {
85   - this.setAceEditorValues();
86   - this.setValuesPending = false;
87   - }
  87 + this.initAceEditors().subscribe(() => {
  88 + if (this.setValuesPending) {
  89 + this.setAceEditorValues();
  90 + this.setValuesPending = false;
  91 + }
  92 + });
88 93 }
89 94
90 95 ngOnDestroy(): void {
... ... @@ -94,7 +99,7 @@ export class CustomActionPrettyResourcesTabsComponent extends PageComponent impl
94 99 ngOnChanges(changes: SimpleChanges): void {
95 100 for (const propName of Object.keys(changes)) {
96 101 if (propName === 'action') {
97   - if (this.aceEditors.length) {
  102 + if (this.aceEditors.length === 2) {
98 103 this.setAceEditorValues();
99 104 } else {
100 105 this.setValuesPending = true;
... ... @@ -152,34 +157,46 @@ export class CustomActionPrettyResourcesTabsComponent extends PageComponent impl
152 157 }
153 158 }
154 159
155   - private initAceEditors() {
  160 + private initAceEditors(): Observable<any> {
156 161 this.aceResize$ = new ResizeObserver((entries) => {
157 162 entries.forEach((entry) => {
158 163 const editor = this.aceEditors.find(aceEditor => aceEditor.container === entry.target);
159 164 this.onAceEditorResize(editor);
160 165 });
161 166 });
162   - this.htmlEditor = this.createAceEditor(this.htmlInputElmRef, 'html');
163   - this.htmlEditor.on('input', () => {
164   - const editorValue = this.htmlEditor.getValue();
165   - if (this.action.customHtml !== editorValue) {
166   - this.action.customHtml = editorValue;
167   - this.notifyActionUpdated();
168   - }
169   - });
170   - this.cssEditor = this.createAceEditor(this.cssInputElmRef, 'css');
171   - this.cssEditor.on('input', () => {
172   - const editorValue = this.cssEditor.getValue();
173   - if (this.action.customCss !== editorValue) {
174   - this.action.customCss = editorValue;
175   - this.notifyActionUpdated();
176   - }
177   - });
  167 + const editorsObservables: Observable<any>[] = [];
  168 +
  169 + editorsObservables.push(this.createAceEditor(this.htmlInputElmRef, 'html').pipe(
  170 + tap((editor) => {
  171 + this.htmlEditor = editor;
  172 + this.htmlEditor.on('input', () => {
  173 + const editorValue = this.htmlEditor.getValue();
  174 + if (this.action.customHtml !== editorValue) {
  175 + this.action.customHtml = editorValue;
  176 + this.notifyActionUpdated();
  177 + }
  178 + });
  179 + })
  180 + ));
  181 +
  182 + editorsObservables.push(this.createAceEditor(this.cssInputElmRef, 'css').pipe(
  183 + tap((editor) => {
  184 + this.cssEditor = editor;
  185 + this.cssEditor.on('input', () => {
  186 + const editorValue = this.cssEditor.getValue();
  187 + if (this.action.customCss !== editorValue) {
  188 + this.action.customCss = editorValue;
  189 + this.notifyActionUpdated();
  190 + }
  191 + });
  192 + })
  193 + ));
  194 + return forkJoin(editorsObservables);
178 195 }
179 196
180   - private createAceEditor(editorElementRef: ElementRef, mode: string): ace.Ace.Editor {
  197 + private createAceEditor(editorElementRef: ElementRef, mode: string): Observable<Ace.Editor> {
181 198 const editorElement = editorElementRef.nativeElement;
182   - let editorOptions: Partial<ace.Ace.EditorOptions> = {
  199 + let editorOptions: Partial<Ace.EditorOptions> = {
183 200 mode: `ace/mode/${mode}`,
184 201 showGutter: true,
185 202 showPrintMargin: true
... ... @@ -190,11 +207,15 @@ export class CustomActionPrettyResourcesTabsComponent extends PageComponent impl
190 207 enableLiveAutocompletion: true
191 208 };
192 209 editorOptions = {...editorOptions, ...advancedOptions};
193   - const aceEditor = ace.edit(editorElement, editorOptions);
194   - aceEditor.session.setUseWrapMode(true);
195   - this.aceEditors.push(aceEditor);
196   - this.aceResize$.observe(editorElement);
197   - return aceEditor;
  210 + return getAce().pipe(
  211 + map((ace) => {
  212 + const aceEditor = ace.edit(editorElement, editorOptions);
  213 + aceEditor.session.setUseWrapMode(true);
  214 + this.aceEditors.push(aceEditor);
  215 + this.aceResize$.observe(editorElement);
  216 + return aceEditor;
  217 + })
  218 + );
198 219 }
199 220
200 221 private setAceEditorValues() {
... ... @@ -202,7 +223,7 @@ export class CustomActionPrettyResourcesTabsComponent extends PageComponent impl
202 223 this.cssEditor.setValue(this.action.customCss ? this.action.customCss : '', -1);
203 224 }
204 225
205   - private onAceEditorResize(aceEditor: ace.Ace.Editor) {
  226 + private onAceEditorResize(aceEditor: Ace.Editor) {
206 227 if (this.editorsResizeCafs[aceEditor.id]) {
207 228 this.editorsResizeCafs[aceEditor.id]();
208 229 delete this.editorsResizeCafs[aceEditor.id];
... ...
... ... @@ -103,7 +103,7 @@ export class GatewayFormComponent extends PageComponent implements OnInit, OnDes
103 103 gatewayType: string;
104 104 gatewayConfigurationGroup: FormGroup;
105 105 securityTypes = SecurityTypeTranslationMap;
106   - gatewayLogLevels = Object.values(GatewayLogLevel);
  106 + gatewayLogLevels = Object.keys(GatewayLogLevel).map(itm => GatewayLogLevel[itm]);
107 107 connectorTypes = Object.keys(ConnectorType);
108 108 storageTypes = StorageTypeTranslationMap;
109 109
... ...
  1 +///
  2 +/// Copyright © 2016-2020 The Thingsboard Authors
  3 +///
  4 +/// Licensed under the Apache License, Version 2.0 (the "License");
  5 +/// you may not use this file except in compliance with the License.
  6 +/// You may obtain a copy of the License at
  7 +///
  8 +/// http://www.apache.org/licenses/LICENSE-2.0
  9 +///
  10 +/// Unless required by applicable law or agreed to in writing, software
  11 +/// distributed under the License is distributed on an "AS IS" BASIS,
  12 +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 +/// See the License for the specific language governing permissions and
  14 +/// limitations under the License.
  15 +///
  16 +
  17 +import { FormattedData, MapProviders, ReplaceInfo } from '@home/components/widget/lib/maps/map-models';
  18 +import {
  19 + createLabelFromDatasource,
  20 + hashCode,
  21 + isDefined,
  22 + isDefinedAndNotNull, isFunction,
  23 + isNumber,
  24 + isUndefined,
  25 + padValue
  26 +} from '@core/utils';
  27 +import { Observable, Observer, of } from 'rxjs';
  28 +import { map } from 'rxjs/operators';
  29 +import { Datasource, DatasourceData } from '@shared/models/widget.models';
  30 +import _ from 'lodash';
  31 +import { mapProviderSchema, providerSets } from '@home/components/widget/lib/maps/schemes';
  32 +import { addCondition, mergeSchemes } from '@core/schema-utils';
  33 +
  34 +export function getProviderSchema(mapProvider: MapProviders, ignoreImageMap = false) {
  35 + const providerSchema = _.cloneDeep(mapProviderSchema);
  36 + if (mapProvider) {
  37 + providerSchema.schema.properties.provider.default = mapProvider;
  38 + }
  39 + if (ignoreImageMap) {
  40 + providerSchema.form[0].items = providerSchema.form[0]?.items.filter(item => item.value !== 'image-map');
  41 + }
  42 + return mergeSchemes([providerSchema,
  43 + ...Object.keys(providerSets)?.map(
  44 + (key: string) => {
  45 + const setting = providerSets[key];
  46 + return addCondition(setting?.schema, `model.provider === '${setting.name}'`);
  47 + })]);
  48 +}
  49 +
  50 +export function getRatio(firsMoment: number, secondMoment: number, intermediateMoment: number): number {
  51 + return (intermediateMoment - firsMoment) / (secondMoment - firsMoment);
  52 +}
  53 +
  54 +export function interpolateOnLineSegment(
  55 + pointA: FormattedData,
  56 + pointB: FormattedData,
  57 + latKeyName: string,
  58 + lngKeyName: string,
  59 + ratio: number
  60 +): { [key: string]: number } {
  61 + return {
  62 + [latKeyName]: (pointA[latKeyName] + (pointB[latKeyName] - pointA[latKeyName]) * ratio),
  63 + [lngKeyName]: (pointA[lngKeyName] + (pointB[lngKeyName] - pointA[lngKeyName]) * ratio)
  64 + };
  65 +}
  66 +
  67 +export function findAngle(startPoint: FormattedData, endPoint: FormattedData, latKeyName: string, lngKeyName: string): number {
  68 + if (isUndefined(startPoint) || isUndefined(endPoint)) {
  69 + return 0;
  70 + }
  71 + let angle = -Math.atan2(endPoint[latKeyName] - startPoint[latKeyName], endPoint[lngKeyName] - startPoint[lngKeyName]);
  72 + angle = angle * 180 / Math.PI;
  73 + return parseInt(angle.toFixed(2), 10);
  74 +}
  75 +
  76 +
  77 +export function getDefCenterPosition(position) {
  78 + if (typeof (position) === 'string') {
  79 + return position.split(',');
  80 + }
  81 + if (typeof (position) === 'object') {
  82 + return position;
  83 + }
  84 + return [0, 0];
  85 +}
  86 +
  87 +
  88 +const imageAspectMap = {};
  89 +
  90 +function imageLoader(imageUrl: string): Observable<HTMLImageElement> {
  91 + return new Observable((observer: Observer<HTMLImageElement>) => {
  92 + const image = document.createElement('img'); // support IE
  93 + image.style.position = 'absolute';
  94 + image.style.left = '-99999px';
  95 + image.style.top = '-99999px';
  96 + image.onload = () => {
  97 + observer.next(image);
  98 + document.body.removeChild(image);
  99 + observer.complete();
  100 + };
  101 + image.onerror = err => {
  102 + observer.error(err);
  103 + document.body.removeChild(image);
  104 + observer.complete();
  105 + };
  106 + document.body.appendChild(image);
  107 + image.src = imageUrl;
  108 + });
  109 +}
  110 +
  111 +export function aspectCache(imageUrl: string): Observable<number> {
  112 + if (imageUrl?.length) {
  113 + const hash = hashCode(imageUrl);
  114 + let aspect = imageAspectMap[hash];
  115 + if (aspect) {
  116 + return of(aspect);
  117 + }
  118 + return imageLoader(imageUrl).pipe(map(image => {
  119 + aspect = image.width / image.height;
  120 + imageAspectMap[hash] = aspect;
  121 + return aspect;
  122 + }));
  123 + }
  124 +}
  125 +
  126 +export type TranslateFunc = (key: string, defaultTranslation?: string) => string;
  127 +
  128 +const varsRegex = /\${([^}]*)}/g;
  129 +const linkActionRegex = /<link-act name=['"]([^['"]*)['"]>([^<]*)<\/link-act>/g;
  130 +const buttonActionRegex = /<button-act name=['"]([^['"]*)['"]>([^<]*)<\/button-act>/g;
  131 +
  132 +function createLinkElement(actionName: string, actionText: string): string {
  133 + return `<a href="javascript:void(0);" class="tb-custom-action" data-action-name=${actionName}>${actionText}</a>`;
  134 +}
  135 +
  136 +function createButtonElement(actionName: string, actionText: string) {
  137 + return `<button mat-button class="tb-custom-action" data-action-name=${actionName}>${actionText}</button>`;
  138 +}
  139 +
  140 +function parseTemplate(template: string, data: { $datasource?: Datasource, [key: string]: any },
  141 + translateFn?: TranslateFunc) {
  142 + let res = '';
  143 + try {
  144 + if (translateFn) {
  145 + template = translateFn(template);
  146 + }
  147 + template = createLabelFromDatasource(data.$datasource, template);
  148 +
  149 + let match = /\${([^}]*)}/g.exec(template);
  150 + while (match !== null) {
  151 + const variable = match[0];
  152 + let label = match[1];
  153 + let valDec = 2;
  154 + const splitValues = label.split(':');
  155 + if (splitValues.length > 1) {
  156 + label = splitValues[0];
  157 + valDec = parseFloat(splitValues[1]);
  158 + }
  159 +
  160 + if (label.startsWith('#')) {
  161 + const keyIndexStr = label.substring(1);
  162 + const n = Math.floor(Number(keyIndexStr));
  163 + if (String(n) === keyIndexStr && n >= 0) {
  164 + label = data.$datasource.dataKeys[n].label;
  165 + }
  166 + }
  167 +
  168 + const value = data[label] || '';
  169 + let textValue: string;
  170 + if (isNumber(value)) {
  171 + textValue = padValue(value, valDec);
  172 + } else {
  173 + textValue = value;
  174 + }
  175 + template = template.replace(variable, textValue);
  176 + match = /\${([^}]*)}/g.exec(template);
  177 + }
  178 +
  179 + let actionTags: string;
  180 + let actionText: string;
  181 + let actionName: string;
  182 + let action: string;
  183 +
  184 + match = linkActionRegex.exec(template);
  185 + while (match !== null) {
  186 + [actionTags, actionName, actionText] = match;
  187 + action = createLinkElement(actionName, actionText);
  188 + template = template.replace(actionTags, action);
  189 + match = linkActionRegex.exec(template);
  190 + }
  191 +
  192 + match = buttonActionRegex.exec(template);
  193 + while (match !== null) {
  194 + [actionTags, actionName, actionText] = match;
  195 + action = createButtonElement(actionName, actionText);
  196 + template = template.replace(actionTags, action);
  197 + match = buttonActionRegex.exec(template);
  198 + }
  199 +
  200 + // const compiled = _.template(template);
  201 + // res = compiled(data);
  202 + res = template;
  203 + } catch (ex) {
  204 + console.log(ex, template);
  205 + }
  206 + return res;
  207 +}
  208 +
  209 +export function processPattern(template: string, data: { $datasource?: Datasource, [key: string]: any }): Array<ReplaceInfo> {
  210 + const replaceInfo = [];
  211 + try {
  212 + const reg = /\${([^}]*)}/g;
  213 + let match = reg.exec(template);
  214 + while (match !== null) {
  215 + const variableInfo: ReplaceInfo = {
  216 + dataKeyName: '',
  217 + valDec: 2,
  218 + variable: ''
  219 + };
  220 + const variable = match[0];
  221 + let label = match[1];
  222 + let valDec = 2;
  223 + const splitValues = label.split(':');
  224 + if (splitValues.length > 1) {
  225 + label = splitValues[0];
  226 + valDec = parseFloat(splitValues[1]);
  227 + }
  228 +
  229 + variableInfo.variable = variable;
  230 + variableInfo.valDec = valDec;
  231 +
  232 + if (label.startsWith('#')) {
  233 + const keyIndexStr = label.substring(1);
  234 + const n = Math.floor(Number(keyIndexStr));
  235 + if (String(n) === keyIndexStr && n >= 0) {
  236 + variableInfo.dataKeyName = data.$datasource.dataKeys[n].label;
  237 + }
  238 + } else {
  239 + variableInfo.dataKeyName = label;
  240 + }
  241 + replaceInfo.push(variableInfo);
  242 +
  243 + match = reg.exec(template);
  244 + }
  245 + } catch (ex) {
  246 + console.log(ex, template);
  247 + }
  248 + return replaceInfo;
  249 +}
  250 +
  251 +export function fillPattern(markerLabelText: string, replaceInfoLabelMarker: Array<ReplaceInfo>, data: FormattedData) {
  252 + let text = createLabelFromDatasource(data.$datasource, markerLabelText);
  253 + if (replaceInfoLabelMarker) {
  254 + for (const variableInfo of replaceInfoLabelMarker) {
  255 + let txtVal = '';
  256 + if (variableInfo.dataKeyName && isDefinedAndNotNull(data[variableInfo.dataKeyName])) {
  257 + const varData = data[variableInfo.dataKeyName];
  258 + if (isNumber(varData)) {
  259 + txtVal = padValue(varData, variableInfo.valDec);
  260 + } else {
  261 + txtVal = varData;
  262 + }
  263 + }
  264 + text = text.replace(variableInfo.variable, txtVal);
  265 + }
  266 + }
  267 + return text;
  268 +}
  269 +
  270 +function prepareProcessPattern(template: string, translateFn?: TranslateFunc): string {
  271 + if (translateFn) {
  272 + template = translateFn(template);
  273 + }
  274 + let actionTags: string;
  275 + let actionText: string;
  276 + let actionName: string;
  277 + let action: string;
  278 +
  279 + let match = linkActionRegex.exec(template);
  280 + while (match !== null) {
  281 + [actionTags, actionName, actionText] = match;
  282 + action = createLinkElement(actionName, actionText);
  283 + template = template.replace(actionTags, action);
  284 + match = linkActionRegex.exec(template);
  285 + }
  286 +
  287 + match = buttonActionRegex.exec(template);
  288 + while (match !== null) {
  289 + [actionTags, actionName, actionText] = match;
  290 + action = createButtonElement(actionName, actionText);
  291 + template = template.replace(actionTags, action);
  292 + match = buttonActionRegex.exec(template);
  293 + }
  294 + return template;
  295 +}
  296 +
  297 +export const parseWithTranslation = {
  298 +
  299 + translateFn: null,
  300 +
  301 + translate(key: string, defaultTranslation?: string): string {
  302 + if (this.translateFn) {
  303 + return this.translateFn(key, defaultTranslation);
  304 + } else {
  305 + throw console.error('Translate not assigned');
  306 + }
  307 + },
  308 + parseTemplate(template: string, data: object, forceTranslate = false): string {
  309 + return parseTemplate(forceTranslate ? this.translate(template) : template, data, this.translate.bind(this));
  310 + },
  311 + prepareProcessPattern(template: string, forceTranslate = false): string {
  312 + return prepareProcessPattern(forceTranslate ? this.translate(template) : template, this.translate.bind(this));
  313 + },
  314 + setTranslate(translateFn: TranslateFunc) {
  315 + this.translateFn = translateFn;
  316 + }
  317 +};
  318 +
  319 +export function parseData(input: DatasourceData[]): FormattedData[] {
  320 + return _(input).groupBy(el => el?.datasource?.entityName)
  321 + .values().value().map((entityArray, i) => {
  322 + const obj: FormattedData = {
  323 + entityName: entityArray[0]?.datasource?.entityName,
  324 + entityId: entityArray[0]?.datasource?.entityId,
  325 + entityType: entityArray[0]?.datasource?.entityType,
  326 + $datasource: entityArray[0]?.datasource,
  327 + dsIndex: i,
  328 + deviceType: null
  329 + };
  330 + entityArray.filter(el => el.data.length).forEach(el => {
  331 + const indexDate = el?.data?.length ? el.data.length - 1 : 0;
  332 + obj[el?.dataKey?.label] = el?.data[indexDate][1];
  333 + obj[el?.dataKey?.label + '|ts'] = el?.data[indexDate][0];
  334 + if (el?.dataKey?.label === 'type') {
  335 + obj.deviceType = el?.data[indexDate][1];
  336 + }
  337 + });
  338 + return obj;
  339 + });
  340 +}
  341 +
  342 +export function parseArray(input: DatasourceData[]): FormattedData[][] {
  343 + return _(input).groupBy(el => el?.datasource?.entityName)
  344 + .values().value().map((entityArray) =>
  345 + entityArray[0].data.map((el, i) => {
  346 + const obj: FormattedData = {
  347 + entityName: entityArray[0]?.datasource?.entityName,
  348 + entityId: entityArray[0]?.datasource?.entityId,
  349 + entityType: entityArray[0]?.datasource?.entityType,
  350 + $datasource: entityArray[0]?.datasource,
  351 + dsIndex: i,
  352 + time: el[0],
  353 + deviceType: null
  354 + };
  355 + entityArray.filter(e => e.data.length && e.data[i]).forEach(entity => {
  356 + obj[entity?.dataKey?.label] = entity?.data[i][1];
  357 + obj[entity?.dataKey?.label + '|ts'] = entity?.data[0][0];
  358 + if (entity?.dataKey?.label === 'type') {
  359 + obj.deviceType = entity?.data[0][1];
  360 + }
  361 + });
  362 + return obj;
  363 + })
  364 + );
  365 +}
  366 +
  367 +export function parseFunction(source: any, params: string[] = ['def']): (...args: any[]) => any {
  368 + let res = null;
  369 + if (source?.length) {
  370 + try {
  371 + res = new Function(...params, source);
  372 + }
  373 + catch (err) {
  374 + res = null;
  375 + }
  376 + }
  377 + return res;
  378 +}
  379 +
  380 +export function safeExecute(func: (...args: any[]) => any, params = []) {
  381 + let res = null;
  382 + if (func && typeof (func) === 'function') {
  383 + try {
  384 + res = func(...params);
  385 + }
  386 + catch (err) {
  387 + console.log('error in external function:', err);
  388 + res = null;
  389 + }
  390 + }
  391 + return res;
  392 +}
  393 +
  394 +export function functionValueCalculator(useFunction: boolean, func: (...args: any[]) => any, params = [], defaultValue: any) {
  395 + let res;
  396 + if (useFunction && isDefined(func) && isFunction(func)) {
  397 + try {
  398 + res = func(...params);
  399 + if (!isDefinedAndNotNull(res) || res === '') {
  400 + res = defaultValue;
  401 + }
  402 + } catch (err) {
  403 + res = defaultValue;
  404 + console.log('error in external function:', err);
  405 + }
  406 + } else {
  407 + res = defaultValue;
  408 + }
  409 + return res;
  410 +}
  411 +
  412 +export function calculateNewPointCoordinate(coordinate: number, imageSize: number): number {
  413 + let pointCoordinate = coordinate / imageSize;
  414 + if (pointCoordinate < 0) {
  415 + pointCoordinate = 0;
  416 + } else if (pointCoordinate > 1) {
  417 + pointCoordinate = 1;
  418 + }
  419 + return pointCoordinate;
  420 +}
  421 +
  422 +export function createLoadingDiv(loadingText: string): JQuery<HTMLElement> {
  423 + return $(`
  424 + <div style="
  425 + z-index: 12;
  426 + position: absolute;
  427 + top: 0;
  428 + bottom: 0;
  429 + left: 0;
  430 + right: 0;
  431 + flex-direction: column;
  432 + align-content: center;
  433 + align-items: center;
  434 + justify-content: center;
  435 + display: flex;
  436 + background: rgba(255,255,255,0.7);
  437 + font-size: 16px;
  438 + font-family: Roboto;
  439 + font-weight: 400;
  440 + text-transform: uppercase;
  441 + ">
  442 + <span>${loadingText}</span>
  443 + </div>
  444 + `);
  445 +}
... ...
... ... @@ -43,12 +43,14 @@ import { Observable, of } from 'rxjs';
43 43 import { Polyline } from './polyline';
44 44 import { Polygon } from './polygon';
45 45 import {
46   - createLoadingDiv,
47 46 createTooltip,
  47 +} from '@home/components/widget/lib/maps/maps-utils';
  48 +import {
  49 + createLoadingDiv,
48 50 parseArray,
49 51 parseData,
50 52 safeExecute
51   -} from '@home/components/widget/lib/maps/maps-utils';
  53 +} from '@home/components/widget/lib/maps/common-maps-utils';
52 54 import { WidgetContext } from '@home/models/widget-component.models';
53 55 import { deepClone, isDefinedAndNotNull, isEmptyStr, isString } from '@core/utils';
54 56
... ... @@ -141,7 +143,7 @@ export default abstract class LeafletMap {
141 143 const header = document.createElement('p');
142 144 header.appendChild(document.createTextNode('Select entity:'));
143 145 header.setAttribute('style', 'font-size: 14px; margin: 8px 0');
144   - datasourcesList.append(header);
  146 + datasourcesList.appendChild(header);
145 147 this.datasources.forEach(ds => {
146 148 const dsItem = document.createElement('p');
147 149 dsItem.appendChild(document.createTextNode(ds.entityName));
... ... @@ -158,9 +160,9 @@ export default abstract class LeafletMap {
158 160 this.createMarker(ds.entityName, updatedEnttity, this.datasources, this.options);
159 161 });
160 162 };
161   - datasourcesList.append(dsItem);
  163 + datasourcesList.appendChild(dsItem);
162 164 });
163   - datasourcesList.append(document.createElement('br'));
  165 + datasourcesList.appendChild(document.createElement('br'));
164 166 const deleteBtn = document.createElement('a');
165 167 deleteBtn.appendChild(document.createTextNode('Discard changes'));
166 168 deleteBtn.onclick = () => {
... ... @@ -170,7 +172,7 @@ export default abstract class LeafletMap {
170 172 this.addMarkers.splice(markerIndex, 1);
171 173 }
172 174 };
173   - datasourcesList.append(deleteBtn);
  175 + datasourcesList.appendChild(deleteBtn);
174 176 const popup = L.popup();
175 177 popup.setContent(datasourcesList);
176 178 newMarker.bindPopup(popup).openPopup();
... ... @@ -222,7 +224,7 @@ export default abstract class LeafletMap {
222 224 const header = document.createElement('p');
223 225 header.appendChild(document.createTextNode('Select entity:'));
224 226 header.setAttribute('style', 'font-size: 14px; margin: 8px 0');
225   - datasourcesList.append(header);
  227 + datasourcesList.appendChild(header);
226 228 this.datasources.forEach(ds => {
227 229 const dsItem = document.createElement('p');
228 230 dsItem.appendChild(document.createTextNode(ds.entityName));
... ... @@ -238,9 +240,9 @@ export default abstract class LeafletMap {
238 240 this.deletePolygon(ds.entityName);
239 241 });
240 242 };
241   - datasourcesList.append(dsItem);
  243 + datasourcesList.appendChild(dsItem);
242 244 });
243   - datasourcesList.append(document.createElement('br'));
  245 + datasourcesList.appendChild(document.createElement('br'));
244 246 const deleteBtn = document.createElement('a');
245 247 deleteBtn.appendChild(document.createTextNode('Discard changes'));
246 248 deleteBtn.onclick = () => {
... ... @@ -250,7 +252,7 @@ export default abstract class LeafletMap {
250 252 this.addPolygons.splice(polygonIndex, 1);
251 253 }
252 254 };
253   - datasourcesList.append(deleteBtn);
  255 + datasourcesList.appendChild(deleteBtn);
254 256 const popup = L.popup();
255 257 popup.setContent(datasourcesList);
256 258 newPolygon.bindPopup(popup).openPopup();
... ... @@ -289,7 +291,7 @@ export default abstract class LeafletMap {
289 291 if (!this.loadingDiv) {
290 292 this.loadingDiv = createLoadingDiv(this.ctx.translate.instant('common.loading'));
291 293 }
292   - this.$container.append(this.loadingDiv[0]);
  294 + this.$container.appendChild(this.loadingDiv[0]);
293 295 } else {
294 296 if (this.loadingDiv) {
295 297 this.loadingDiv.remove();
... ... @@ -609,14 +611,13 @@ export default abstract class LeafletMap {
609 611 }
610 612
611 613 updatePoints(pointsData: FormattedData[][], getTooltip: (point: FormattedData) => string) {
612   - if(pointsData.length) {
  614 + if (pointsData.length) {
613 615 if (this.points) {
614 616 this.map.removeLayer(this.points);
615 617 }
616 618 this.points = new FeatureGroup();
617 619 }
618   - for(let i = 0; i < pointsData.length; i++) {
619   - const pointsList = pointsData[i];
  620 + for (const pointsList of pointsData) {
620 621 pointsList.filter(pdata => !!this.convertPosition(pdata)).forEach(data => {
621 622 const point = L.circleMarker(this.convertPosition(data), {
622 623 color: this.options.pointColor,
... ... @@ -630,7 +631,7 @@ export default abstract class LeafletMap {
630 631 this.points.addLayer(point);
631 632 });
632 633 }
633   - if(pointsData.length) {
  634 + if (pointsData.length) {
634 635 this.map.addLayer(this.points);
635 636 }
636 637 }
... ...
... ... @@ -14,7 +14,6 @@
14 14 /// limitations under the License.
15 15 ///
16 16
17   -import { LatLngTuple } from 'leaflet';
18 17 import { Datasource } from '@app/shared/models/widget.models';
19 18 import { EntityType } from '@shared/models/entity-type.models';
20 19 import tinycolor from 'tinycolor2';
... ... @@ -47,7 +46,7 @@ export type MapSettings = {
47 46 provider?: MapProviders;
48 47 credentials?: any; // declare credentials format
49 48 gmApiKey?: string;
50   - defaultCenterPosition?: LatLngTuple;
  49 + defaultCenterPosition?: [number, number];
51 50 markerClusteringSetting?;
52 51 useDefaultCenterPosition?: boolean;
53 52 gmDefaultMapType?: string;
... ...
... ... @@ -15,9 +15,10 @@
15 15 ///
16 16
17 17 import { JsonSettingsSchema } from '@shared/models/widget.models';
18   -import { MapProviders } from './map-models';
  18 +import { MapProviders } from '@home/components/widget/lib/maps/map-models';
19 19
20 20 export interface MapWidgetInterface {
  21 + map?: any;
21 22 resize();
22 23 update();
23 24 onInit();
... ...
... ... @@ -31,20 +31,25 @@ import {
31 31 routeMapSettingsSchema
32 32 } from './schemes';
33 33 import { MapWidgetInterface, MapWidgetStaticInterface } from './map-widget.interface';
34   -import { addCondition, addGroupInfo, addToSchema, initSchema, mergeSchemes } from '@core/schema-utils';
  34 +import {
  35 + addCondition,
  36 + addGroupInfo,
  37 + addToSchema,
  38 + initSchema,
  39 + mergeSchemes
  40 +} from '@core/schema-utils';
35 41 import { WidgetContext } from '@app/modules/home/models/widget-component.models';
36   -import { getDefCenterPosition, parseFunction, parseWithTranslation } from './maps-utils';
  42 +import { getDefCenterPosition, getProviderSchema, parseFunction, parseWithTranslation } from './common-maps-utils';
37 43 import { Datasource, DatasourceData, JsonSettingsSchema, WidgetActionDescriptor } from '@shared/models/widget.models';
38 44 import { EntityId } from '@shared/models/id/entity-id';
39 45 import { AttributeScope, DataKeyType, LatestTelemetry } from '@shared/models/telemetry/telemetry.models';
40 46 import { AttributeService } from '@core/http/attribute.service';
41 47 import { TranslateService } from '@ngx-translate/core';
42 48 import { UtilsService } from '@core/services/utils.service';
43   -import _ from 'lodash';
44 49 import { EntityDataPageLink } from '@shared/models/query/query.models';
45 50 import { isDefined } from '@core/utils';
46 51 import { forkJoin, Observable, of } from 'rxjs';
47   -import { providerSets } from '@home/components/widget/lib/maps/providers';
  52 +import { providerClass } from '@home/components/widget/lib/maps/providers';
48 53
49 54 // @dynamic
50 55 export class MapWidgetController implements MapWidgetInterface {
... ... @@ -70,7 +75,7 @@ export class MapWidgetController implements MapWidgetInterface {
70 75 this.settings.markerClick = this.getDescriptors('markerClick');
71 76 this.settings.polygonClick = this.getDescriptors('polygonClick');
72 77
73   - const MapClass = providerSets[this.provider]?.MapClass;
  78 + const MapClass = providerClass[this.provider];
74 79 if (!MapClass) {
75 80 return;
76 81 }
... ... @@ -101,19 +106,7 @@ export class MapWidgetController implements MapWidgetInterface {
101 106 }
102 107
103 108 public static getProvidersSchema(mapProvider: MapProviders, ignoreImageMap = false) {
104   - const providerSchema = _.cloneDeep(mapProviderSchema);
105   - if (mapProvider) {
106   - providerSchema.schema.properties.provider.default = mapProvider;
107   - }
108   - if (ignoreImageMap) {
109   - providerSchema.form[0].items = providerSchema.form[0]?.items.filter(item => item.value !== 'image-map');
110   - }
111   - return mergeSchemes([providerSchema,
112   - ...Object.keys(providerSets)?.map(
113   - (key: string) => {
114   - const setting = providerSets[key];
115   - return addCondition(setting?.schema, `model.provider === '${setting.name}'`);
116   - })]);
  109 + return getProviderSchema(mapProvider, ignoreImageMap);
117 110 }
118 111
119 112 public static settingsSchema(mapProvider: MapProviders, drawRoutes: boolean): JsonSettingsSchema {
... ...
... ... @@ -15,20 +15,8 @@
15 15 ///
16 16
17 17 import L from 'leaflet';
18   -import { FormattedData, MarkerSettings, PolygonSettings, PolylineSettings, ReplaceInfo } from './map-models';
19   -import { Datasource, DatasourceData } from '@app/shared/models/widget.models';
20   -import _ from 'lodash';
21   -import { Observable, Observer, of } from 'rxjs';
22   -import { map } from 'rxjs/operators';
23   -import {
24   - createLabelFromDatasource,
25   - hashCode,
26   - isDefined,
27   - isDefinedAndNotNull, isFunction,
28   - isNumber,
29   - isUndefined,
30   - padValue
31   -} from '@core/utils';
  18 +import { MarkerSettings, PolygonSettings, PolylineSettings } from './map-models';
  19 +import { Datasource } from '@app/shared/models/widget.models';
32 20
33 21 export function createTooltip(target: L.Layer,
34 22 settings: MarkerSettings | PolylineSettings | PolygonSettings,
... ... @@ -68,400 +56,3 @@ export function bindPopupActions(popup: L.Popup, settings: MarkerSettings | Poly
68 56 }
69 57 });
70 58 }
71   -
72   -export function getRatio(firsMoment: number, secondMoment: number, intermediateMoment: number): number {
73   - return (intermediateMoment - firsMoment) / (secondMoment - firsMoment);
74   -}
75   -
76   -export function interpolateOnLineSegment(
77   - pointA: FormattedData,
78   - pointB: FormattedData,
79   - latKeyName: string,
80   - lngKeyName: string,
81   - ratio: number
82   -): { [key: string]: number } {
83   - return {
84   - [latKeyName]: (pointA[latKeyName] + (pointB[latKeyName] - pointA[latKeyName]) * ratio),
85   - [lngKeyName]: (pointA[lngKeyName] + (pointB[lngKeyName] - pointA[lngKeyName]) * ratio)
86   - };
87   -}
88   -
89   -export function findAngle(startPoint: FormattedData, endPoint: FormattedData, latKeyName: string, lngKeyName: string): number {
90   - if (isUndefined(startPoint) || isUndefined(endPoint)) {
91   - return 0;
92   - }
93   - let angle = -Math.atan2(endPoint[latKeyName] - startPoint[latKeyName], endPoint[lngKeyName] - startPoint[lngKeyName]);
94   - angle = angle * 180 / Math.PI;
95   - return parseInt(angle.toFixed(2), 10);
96   -}
97   -
98   -
99   -export function getDefCenterPosition(position) {
100   - if (typeof (position) === 'string') {
101   - return position.split(',');
102   - }
103   - if (typeof (position) === 'object') {
104   - return position;
105   - }
106   - return [0, 0];
107   -}
108   -
109   -
110   -const imageAspectMap = {};
111   -
112   -function imageLoader(imageUrl: string): Observable<HTMLImageElement> {
113   - return new Observable((observer: Observer<HTMLImageElement>) => {
114   - const image = document.createElement('img'); // support IE
115   - image.style.position = 'absolute';
116   - image.style.left = '-99999px';
117   - image.style.top = '-99999px';
118   - image.onload = () => {
119   - observer.next(image);
120   - document.body.removeChild(image);
121   - observer.complete();
122   - };
123   - image.onerror = err => {
124   - observer.error(err);
125   - document.body.removeChild(image);
126   - observer.complete();
127   - };
128   - document.body.appendChild(image);
129   - image.src = imageUrl;
130   - });
131   -}
132   -
133   -export function aspectCache(imageUrl: string): Observable<number> {
134   - if (imageUrl?.length) {
135   - const hash = hashCode(imageUrl);
136   - let aspect = imageAspectMap[hash];
137   - if (aspect) {
138   - return of(aspect);
139   - }
140   - return imageLoader(imageUrl).pipe(map(image => {
141   - aspect = image.width / image.height;
142   - imageAspectMap[hash] = aspect;
143   - return aspect;
144   - }));
145   - }
146   -}
147   -
148   -export type TranslateFunc = (key: string, defaultTranslation?: string) => string;
149   -
150   -const varsRegex = /\${([^}]*)}/g;
151   -const linkActionRegex = /<link-act name=['"]([^['"]*)['"]>([^<]*)<\/link-act>/g;
152   -const buttonActionRegex = /<button-act name=['"]([^['"]*)['"]>([^<]*)<\/button-act>/g;
153   -
154   -function createLinkElement(actionName: string, actionText: string): string {
155   - return `<a href="javascript:void(0);" class="tb-custom-action" data-action-name=${actionName}>${actionText}</a>`;
156   -}
157   -
158   -function createButtonElement(actionName: string, actionText: string) {
159   - return `<button mat-button class="tb-custom-action" data-action-name=${actionName}>${actionText}</button>`;
160   -}
161   -
162   -function parseTemplate(template: string, data: { $datasource?: Datasource, [key: string]: any },
163   - translateFn?: TranslateFunc) {
164   - let res = '';
165   - try {
166   - if (translateFn) {
167   - template = translateFn(template);
168   - }
169   - template = createLabelFromDatasource(data.$datasource, template);
170   -
171   - let match = /\${([^}]*)}/g.exec(template);
172   - while (match !== null) {
173   - const variable = match[0];
174   - let label = match[1];
175   - let valDec = 2;
176   - const splitValues = label.split(':');
177   - if (splitValues.length > 1) {
178   - label = splitValues[0];
179   - valDec = parseFloat(splitValues[1]);
180   - }
181   -
182   - if (label.startsWith('#')) {
183   - const keyIndexStr = label.substring(1);
184   - const n = Math.floor(Number(keyIndexStr));
185   - if (String(n) === keyIndexStr && n >= 0) {
186   - label = data.$datasource.dataKeys[n].label;
187   - }
188   - }
189   -
190   - const value = data[label] || '';
191   - let textValue: string;
192   - if (isNumber(value)) {
193   - textValue = padValue(value, valDec);
194   - } else {
195   - textValue = value;
196   - }
197   - template = template.replace(variable, textValue);
198   - match = /\${([^}]*)}/g.exec(template);
199   - }
200   -
201   - let actionTags: string;
202   - let actionText: string;
203   - let actionName: string;
204   - let action: string;
205   -
206   - match = linkActionRegex.exec(template);
207   - while (match !== null) {
208   - [actionTags, actionName, actionText] = match;
209   - action = createLinkElement(actionName, actionText);
210   - template = template.replace(actionTags, action);
211   - match = linkActionRegex.exec(template);
212   - }
213   -
214   - match = buttonActionRegex.exec(template);
215   - while (match !== null) {
216   - [actionTags, actionName, actionText] = match;
217   - action = createButtonElement(actionName, actionText);
218   - template = template.replace(actionTags, action);
219   - match = buttonActionRegex.exec(template);
220   - }
221   -
222   - // const compiled = _.template(template);
223   - // res = compiled(data);
224   - res = template;
225   - } catch (ex) {
226   - console.log(ex, template);
227   - }
228   - return res;
229   -}
230   -
231   -export function processPattern(template: string, data: { $datasource?: Datasource, [key: string]: any }): Array<ReplaceInfo> {
232   - const replaceInfo = [];
233   - try {
234   - const reg = /\${([^}]*)}/g;
235   - let match = reg.exec(template);
236   - while (match !== null) {
237   - const variableInfo: ReplaceInfo = {
238   - dataKeyName: '',
239   - valDec: 2,
240   - variable: ''
241   - };
242   - const variable = match[0];
243   - let label = match[1];
244   - let valDec = 2;
245   - const splitValues = label.split(':');
246   - if (splitValues.length > 1) {
247   - label = splitValues[0];
248   - valDec = parseFloat(splitValues[1]);
249   - }
250   -
251   - variableInfo.variable = variable;
252   - variableInfo.valDec = valDec;
253   -
254   - if (label.startsWith('#')) {
255   - const keyIndexStr = label.substring(1);
256   - const n = Math.floor(Number(keyIndexStr));
257   - if (String(n) === keyIndexStr && n >= 0) {
258   - variableInfo.dataKeyName = data.$datasource.dataKeys[n].label;
259   - }
260   - } else {
261   - variableInfo.dataKeyName = label;
262   - }
263   - replaceInfo.push(variableInfo);
264   -
265   - match = reg.exec(template);
266   - }
267   - } catch (ex) {
268   - console.log(ex, template);
269   - }
270   - return replaceInfo;
271   -}
272   -
273   -export function fillPattern(markerLabelText: string, replaceInfoLabelMarker: Array<ReplaceInfo>, data: FormattedData) {
274   - let text = createLabelFromDatasource(data.$datasource, markerLabelText);
275   - if (replaceInfoLabelMarker) {
276   - for (const variableInfo of replaceInfoLabelMarker) {
277   - let txtVal = '';
278   - if (variableInfo.dataKeyName && isDefinedAndNotNull(data[variableInfo.dataKeyName])) {
279   - const varData = data[variableInfo.dataKeyName];
280   - if (isNumber(varData)) {
281   - txtVal = padValue(varData, variableInfo.valDec);
282   - } else {
283   - txtVal = varData;
284   - }
285   - }
286   - text = text.replace(variableInfo.variable, txtVal);
287   - }
288   - }
289   - return text;
290   -}
291   -
292   -function prepareProcessPattern(template: string, translateFn?: TranslateFunc): string {
293   - if (translateFn) {
294   - template = translateFn(template);
295   - }
296   - let actionTags: string;
297   - let actionText: string;
298   - let actionName: string;
299   - let action: string;
300   -
301   - let match = linkActionRegex.exec(template);
302   - while (match !== null) {
303   - [actionTags, actionName, actionText] = match;
304   - action = createLinkElement(actionName, actionText);
305   - template = template.replace(actionTags, action);
306   - match = linkActionRegex.exec(template);
307   - }
308   -
309   - match = buttonActionRegex.exec(template);
310   - while (match !== null) {
311   - [actionTags, actionName, actionText] = match;
312   - action = createButtonElement(actionName, actionText);
313   - template = template.replace(actionTags, action);
314   - match = buttonActionRegex.exec(template);
315   - }
316   - return template;
317   -}
318   -
319   -export const parseWithTranslation = {
320   -
321   - translateFn: null,
322   -
323   - translate(key: string, defaultTranslation?: string): string {
324   - if (this.translateFn) {
325   - return this.translateFn(key, defaultTranslation);
326   - } else {
327   - throw console.error('Translate not assigned');
328   - }
329   - },
330   - parseTemplate(template: string, data: object, forceTranslate = false): string {
331   - return parseTemplate(forceTranslate ? this.translate(template) : template, data, this.translate.bind(this));
332   - },
333   - prepareProcessPattern(template: string, forceTranslate = false): string {
334   - return prepareProcessPattern(forceTranslate ? this.translate(template) : template, this.translate.bind(this));
335   - },
336   - setTranslate(translateFn: TranslateFunc) {
337   - this.translateFn = translateFn;
338   - }
339   -};
340   -
341   -export function parseData(input: DatasourceData[]): FormattedData[] {
342   - return _(input).groupBy(el => el?.datasource?.entityName)
343   - .values().value().map((entityArray, i) => {
344   - const obj: FormattedData = {
345   - entityName: entityArray[0]?.datasource?.entityName,
346   - entityId: entityArray[0]?.datasource?.entityId,
347   - entityType: entityArray[0]?.datasource?.entityType,
348   - $datasource: entityArray[0]?.datasource,
349   - dsIndex: i,
350   - deviceType: null
351   - };
352   - entityArray.filter(el => el.data.length).forEach(el => {
353   - const indexDate = el?.data?.length ? el.data.length - 1 : 0;
354   - obj[el?.dataKey?.label] = el?.data[indexDate][1];
355   - obj[el?.dataKey?.label + '|ts'] = el?.data[indexDate][0];
356   - if (el?.dataKey?.label === 'type') {
357   - obj.deviceType = el?.data[indexDate][1];
358   - }
359   - });
360   - return obj;
361   - });
362   -}
363   -
364   -export function parseArray(input: DatasourceData[]): FormattedData[][] {
365   - return _(input).groupBy(el => el?.datasource?.entityName)
366   - .values().value().map((entityArray) =>
367   - entityArray[0].data.map((el, i) => {
368   - const obj: FormattedData = {
369   - entityName: entityArray[0]?.datasource?.entityName,
370   - entityId: entityArray[0]?.datasource?.entityId,
371   - entityType: entityArray[0]?.datasource?.entityType,
372   - $datasource: entityArray[0]?.datasource,
373   - dsIndex: i,
374   - time: el[0],
375   - deviceType: null
376   - };
377   - entityArray.filter(e => e.data.length && e.data[i]).forEach(entity => {
378   - obj[entity?.dataKey?.label] = entity?.data[i][1];
379   - obj[entity?.dataKey?.label + '|ts'] = entity?.data[0][0];
380   - if (entity?.dataKey?.label === 'type') {
381   - obj.deviceType = entity?.data[0][1];
382   - }
383   - });
384   - return obj;
385   - })
386   - );
387   -}
388   -
389   -export function parseFunction(source: any, params: string[] = ['def']): (...args: any[]) => any {
390   - let res = null;
391   - if (source?.length) {
392   - try {
393   - res = new Function(...params, source);
394   - }
395   - catch (err) {
396   - res = null;
397   - }
398   - }
399   - return res;
400   -}
401   -
402   -export function safeExecute(func: (...args: any[]) => any, params = []) {
403   - let res = null;
404   - if (func && typeof (func) === 'function') {
405   - try {
406   - res = func(...params);
407   - }
408   - catch (err) {
409   - console.log('error in external function:', err);
410   - res = null;
411   - }
412   - }
413   - return res;
414   -}
415   -
416   -export function functionValueCalculator(useFunction: boolean, func: (...args: any[]) => any, params = [], defaultValue: any) {
417   - let res;
418   - if (useFunction && isDefined(func) && isFunction(func)) {
419   - try {
420   - res = func(...params);
421   - if (!isDefinedAndNotNull(res) || res === '') {
422   - res = defaultValue;
423   - }
424   - } catch (err) {
425   - res = defaultValue;
426   - console.log('error in external function:', err);
427   - }
428   - } else {
429   - res = defaultValue;
430   - }
431   - return res;
432   -}
433   -
434   -export function calculateNewPointCoordinate(coordinate: number, imageSize: number): number {
435   - let pointCoordinate = coordinate / imageSize;
436   - if (pointCoordinate < 0) {
437   - pointCoordinate = 0;
438   - } else if (pointCoordinate > 1) {
439   - pointCoordinate = 1;
440   - }
441   - return pointCoordinate;
442   -}
443   -
444   -export function createLoadingDiv(loadingText: string): JQuery<HTMLElement> {
445   - return $(`
446   - <div style="
447   - z-index: 12;
448   - position: absolute;
449   - top: 0;
450   - bottom: 0;
451   - left: 0;
452   - right: 0;
453   - flex-direction: column;
454   - align-content: center;
455   - align-items: center;
456   - justify-content: center;
457   - display: flex;
458   - background: rgba(255,255,255,0.7);
459   - font-size: 16px;
460   - font-family: Roboto;
461   - font-weight: 400;
462   - text-transform: uppercase;
463   - ">
464   - <span>${loadingText}</span>
465   - </div>
466   - `);
467   -}
... ...
... ... @@ -17,14 +17,16 @@
17 17 import L, { Icon, LeafletMouseEvent } from 'leaflet';
18 18 import { FormattedData, MarkerSettings } from './map-models';
19 19 import {
20   - aspectCache,
21 20 bindPopupActions,
22 21 createTooltip,
  22 +} from './maps-utils';
  23 +import {
  24 + aspectCache,
23 25 fillPattern,
24 26 parseWithTranslation,
25 27 processPattern,
26 28 safeExecute
27   -} from './maps-utils';
  29 +} from './common-maps-utils';
28 30 import tinycolor from 'tinycolor2';
29 31 import { isDefined, isDefinedAndNotNull } from '@core/utils';
30 32 import LeafletMap from './leaflet-map';
... ...
... ... @@ -15,7 +15,8 @@
15 15 ///
16 16
17 17 import L, { LatLngExpression, LeafletMouseEvent } from 'leaflet';
18   -import { createTooltip, functionValueCalculator, parseWithTranslation, safeExecute } from './maps-utils';
  18 +import { createTooltip } from './maps-utils';
  19 +import { functionValueCalculator, parseWithTranslation, safeExecute } from './common-maps-utils';
19 20 import 'leaflet-editable/src/Leaflet.Editable';
20 21 import { FormattedData, PolygonSettings } from './map-models';
21 22
... ...
... ... @@ -18,7 +18,7 @@ import L, { PolylineDecoratorOptions } from 'leaflet';
18 18 import 'leaflet-polylinedecorator';
19 19
20 20 import { FormattedData, PolylineSettings } from './map-models';
21   -import { functionValueCalculator, safeExecute } from '@home/components/widget/lib/maps/maps-utils';
  21 +import { functionValueCalculator } from '@home/components/widget/lib/maps/common-maps-utils';
22 22
23 23 export class Polyline {
24 24
... ...
... ... @@ -19,7 +19,7 @@ import LeafletMap from '../leaflet-map';
19 19 import { MapImage, PosFuncton, UnitedMapSettings } from '../map-models';
20 20 import { Observable, ReplaySubject } from 'rxjs';
21 21 import { filter, map, mergeMap } from 'rxjs/operators';
22   -import { aspectCache, calculateNewPointCoordinate, parseFunction } from '@home/components/widget/lib/maps/maps-utils';
  22 +import { aspectCache, calculateNewPointCoordinate, parseFunction } from '@home/components/widget/lib/maps/common-maps-utils';
23 23 import { WidgetContext } from '@home/models/widget-component.models';
24 24 import { DataSet, DatasourceType, widgetType } from '@shared/models/widget.models';
25 25 import { DataKeyType } from '@shared/models/telemetry/telemetry.models';
... ...
... ... @@ -14,11 +14,6 @@
14 14 /// limitations under the License.
15 15 ///
16 16
17   -import {
18   - googleMapSettingsSchema, hereMapSettingsSchema, imageMapSettingsSchema,
19   - openstreetMapSettingsSchema,
20   - tencentMapSettingsSchema
21   -} from '@home/components/widget/lib/maps/schemes';
22 17 import { OpenStreetMap } from './openstreet-map';
23 18 import { TencentMap } from './tencent-map';
24 19 import { GoogleMap } from './google-map';
... ... @@ -26,38 +21,11 @@ import { HEREMap } from './here-map';
26 21 import { ImageMap } from './image-map';
27 22 import { Type } from '@angular/core';
28 23 import LeafletMap from '@home/components/widget/lib/maps/leaflet-map';
29   -import { JsonSettingsSchema } from '@shared/models/widget.models';
30 24
31   -interface IProvider {
32   - MapClass: Type<LeafletMap>;
33   - schema: JsonSettingsSchema;
34   - name: string;
35   -}
36   -
37   -export const providerSets: { [key: string]: IProvider } = {
38   - 'openstreet-map': {
39   - MapClass: OpenStreetMap,
40   - schema: openstreetMapSettingsSchema,
41   - name: 'openstreet-map'
42   - },
43   - 'tencent-map': {
44   - MapClass: TencentMap,
45   - schema: tencentMapSettingsSchema,
46   - name: 'tencent-map'
47   - },
48   - 'google-map': {
49   - MapClass: GoogleMap,
50   - schema: googleMapSettingsSchema,
51   - name: 'google-map'
52   - },
53   - here: {
54   - MapClass: HEREMap,
55   - schema: hereMapSettingsSchema,
56   - name: 'here'
57   - },
58   - 'image-map': {
59   - MapClass: ImageMap,
60   - schema: imageMapSettingsSchema,
61   - name: 'image-map'
62   - }
  25 +export const providerClass: { [key: string]: Type<LeafletMap> } = {
  26 + 'openstreet-map': OpenStreetMap,
  27 + 'tencent-map': TencentMap,
  28 + 'google-map': GoogleMap,
  29 + here: HEREMap,
  30 + 'image-map': ImageMap
63 31 };
... ...
... ... @@ -14,6 +14,8 @@
14 14 /// limitations under the License.
15 15 ///
16 16
  17 +import { JsonSettingsSchema } from '@shared/models/widget.models';
  18 +
17 19 export const googleMapSettingsSchema =
18 20 {
19 21 schema: {
... ... @@ -1096,3 +1098,31 @@ export const tripAnimationSchema = {
1096 1098 ]
1097 1099 }]
1098 1100 };
  1101 +
  1102 +interface IProvider {
  1103 + schema: JsonSettingsSchema;
  1104 + name: string;
  1105 +}
  1106 +
  1107 +export const providerSets: { [key: string]: IProvider } = {
  1108 + 'openstreet-map': {
  1109 + schema: openstreetMapSettingsSchema,
  1110 + name: 'openstreet-map'
  1111 + },
  1112 + 'tencent-map': {
  1113 + schema: tencentMapSettingsSchema,
  1114 + name: 'tencent-map'
  1115 + },
  1116 + 'google-map': {
  1117 + schema: googleMapSettingsSchema,
  1118 + name: 'google-map'
  1119 + },
  1120 + here: {
  1121 + schema: hereMapSettingsSchema,
  1122 + name: 'here'
  1123 + },
  1124 + 'image-map': {
  1125 + schema: imageMapSettingsSchema,
  1126 + name: 'image-map'
  1127 + }
  1128 +};
... ...
... ... @@ -21,7 +21,7 @@ import { UtilsService } from '@core/services/utils.service';
21 21 import { Store } from '@ngrx/store';
22 22 import { AppState } from '@core/core.state';
23 23 import { isDefined, isNumber } from '@core/utils';
24   -import { CanvasDigitalGauge, CanvasDigitalGaugeOptions } from '@home/components/widget/lib/canvas-digital-gauge';
  24 +import { CanvasDigitalGaugeOptions } from '@home/components/widget/lib/canvas-digital-gauge';
25 25 import * as tinycolor_ from 'tinycolor2';
26 26 import { ResizeObserver } from '@juggle/resize-observer';
27 27 import GenericOptions = CanvasGauges.GenericOptions;
... ... @@ -102,7 +102,7 @@ export class KnobComponent extends PageComponent implements OnInit, OnDestroy {
102 102 private textMeasure: JQuery<HTMLElement>;
103 103 private canvasBarElement: HTMLElement;
104 104
105   - private canvasBar: CanvasDigitalGauge;
  105 + private canvasBar: any; // CanvasDigitalGauge;
106 106
107 107 private knobResize$: ResizeObserver;
108 108
... ... @@ -165,7 +165,6 @@ export class KnobComponent extends PageComponent implements OnInit, OnDestroy {
165 165 animation: false
166 166 };
167 167
168   - this.canvasBar = new CanvasDigitalGauge(canvasBarData).draw();
169 168
170 169 this.knob.on('click', (e) => {
171 170 if (this.moving) {
... ... @@ -277,9 +276,6 @@ export class KnobComponent extends PageComponent implements OnInit, OnDestroy {
277 276
278 277 });
279 278
280   - const initialValue = isDefined(settings.initialValue) ? settings.initialValue : this.minValue;
281   - this.setValue(initialValue);
282   -
283 279 const subscription = this.ctx.defaultSubscription;
284 280 const rpcEnabled = subscription.rpcEnabled;
285 281
... ... @@ -297,13 +293,22 @@ export class KnobComponent extends PageComponent implements OnInit, OnDestroy {
297 293 if (settings.setValueMethod && settings.setValueMethod.length) {
298 294 this.setValueMethod = settings.setValueMethod;
299 295 }
300   - if (!rpcEnabled) {
301   - this.onError('Target device is not set!');
302   - } else {
303   - if (!this.isSimulated) {
304   - this.rpcRequestValue();
  296 +
  297 + import('@home/components/widget/lib/canvas-digital-gauge').then(
  298 + (gauge) => {
  299 + this.canvasBar = new gauge.CanvasDigitalGauge(canvasBarData).draw();
  300 + const initialValue = isDefined(settings.initialValue) ? settings.initialValue : this.minValue;
  301 + this.setValue(initialValue);
  302 + if (!rpcEnabled) {
  303 + this.onError('Target device is not set!');
  304 + } else {
  305 + if (!this.isSimulated) {
  306 + this.rpcRequestValue();
  307 + }
  308 + }
305 309 }
306   - }
  310 + );
  311 +
307 312 }
308 313
309 314 private degreeToRatio(degree: number): number {
... ... @@ -328,7 +333,9 @@ export class KnobComponent extends PageComponent implements OnInit, OnDestroy {
328 333 const height = this.knobContainer.height();
329 334 const size = Math.min(width, height);
330 335 this.knob.css({width: size, height: size});
331   - this.canvasBar.update({width: size, height: size} as GenericOptions);
  336 + if (this.canvasBar) {
  337 + this.canvasBar.update({width: size, height: size} as GenericOptions);
  338 + }
332 339 this.setFontSize(this.knobTitle, this.title, this.knobTitleContainer.height(), this.knobTitleContainer.width());
333 340 this.setFontSize(this.knobError, this.error, this.knobErrorContainer.height(), this.knobErrorContainer.width());
334 341 const minmaxHeight = this.knobMinmaxContainer.height();
... ...
... ... @@ -546,7 +546,7 @@ class TimeseriesDatasource implements DataSource<TimeseriesRow> {
546 546 }
547 547 }
548 548 } else {
549   - rows = Object.values(rowsMap);
  549 + rows = Object.keys(rowsMap).map(itm => rowsMap[itm]);
550 550 }
551 551 return rows;
552 552 }
... ...
... ... @@ -27,28 +27,28 @@ import {
27 27 SecurityContext,
28 28 ViewChild
29 29 } from '@angular/core';
30   -import { MapWidgetController, TbMapWidgetV2 } from '../lib/maps/map-widget2';
31   -import { FormattedData, MapProviders, TripAnimationSettings } from '../lib/maps/map-models';
  30 +import { FormattedData, MapProviders, TripAnimationSettings } from '@home/components/widget/lib/maps/map-models';
32 31 import { addCondition, addGroupInfo, addToSchema, initSchema } from '@app/core/schema-utils';
33   -import { mapPolygonSchema, pathSchema, pointSchema, tripAnimationSchema } from '../lib/maps/schemes';
  32 +import { mapPolygonSchema, pathSchema, pointSchema, tripAnimationSchema } from '@home/components/widget/lib/maps/schemes';
34 33 import { DomSanitizer } from '@angular/platform-browser';
35 34 import { WidgetContext } from '@app/modules/home/models/widget-component.models';
36 35 import {
37   - findAngle,
  36 + findAngle, getProviderSchema,
38 37 getRatio,
39 38 interpolateOnLineSegment,
40 39 parseArray,
41 40 parseFunction,
42 41 parseWithTranslation,
43 42 safeExecute
44   -} from '../lib/maps/maps-utils';
  43 +} from '@home/components/widget/lib/maps/common-maps-utils';
45 44 import { JsonSettingsSchema, WidgetConfig } from '@shared/models/widget.models';
46 45 import moment from 'moment';
47 46 import { isUndefined } from '@core/utils';
48 47 import { ResizeObserver } from '@juggle/resize-observer';
  48 +import { MapWidgetInterface } from '@home/components/widget/lib/maps/map-widget.interface';
49 49
50   -interface dataMap {
51   - [key: string] : FormattedData
  50 +interface DataMap {
  51 + [key: string]: FormattedData;
52 52 }
53 53
54 54 @Component({
... ... @@ -67,7 +67,7 @@ export class TripAnimationComponent implements OnInit, AfterViewInit, OnDestroy
67 67
68 68 @ViewChild('map') mapContainer;
69 69
70   - mapWidget: MapWidgetController;
  70 + mapWidget: MapWidgetInterface;
71 71 historicalData: FormattedData[][];
72 72 normalizationStep: number;
73 73 interpolatedTimeData = [];
... ... @@ -85,7 +85,7 @@ export class TripAnimationComponent implements OnInit, AfterViewInit, OnDestroy
85 85
86 86 static getSettingsSchema(): JsonSettingsSchema {
87 87 const schema = initSchema();
88   - addToSchema(schema, TbMapWidgetV2.getProvidersSchema(null, true));
  88 + addToSchema(schema, getProviderSchema(null, true));
89 89 addGroupInfo(schema, 'Map Provider Settings');
90 90 addToSchema(schema, tripAnimationSchema);
91 91 addGroupInfo(schema, 'Trip Animation Settings');
... ... @@ -106,7 +106,7 @@ export class TripAnimationComponent implements OnInit, AfterViewInit, OnDestroy
106 106 buttonColor: tinycolor(this.widgetConfig.color).setAlpha(0.54).toRgbString(),
107 107 disabledButtonColor: tinycolor(this.widgetConfig.color).setAlpha(0.3).toRgbString(),
108 108 rotationAngle: 0
109   - }
  109 + };
110 110 this.settings = { ...settings, ...this.ctx.settings };
111 111 this.useAnchors = this.settings.showPoints && this.settings.usePointAsAnchor;
112 112 this.settings.pointAsAnchorFunction = parseFunction(this.settings.pointAsAnchorFunction, ['data', 'dsData', 'dsIndex']);
... ... @@ -122,15 +122,19 @@ export class TripAnimationComponent implements OnInit, AfterViewInit, OnDestroy
122 122 }
123 123 this.mapWidget.map.map?.invalidateSize();
124 124 this.cd.detectChanges();
125   - }
  125 + };
126 126 }
127 127
128 128 ngAfterViewInit() {
129   - this.mapWidget = new MapWidgetController(MapProviders.openstreet, false, this.ctx, this.mapContainer.nativeElement);
130   - this.mapResize$ = new ResizeObserver(() => {
131   - this.mapWidget.resize();
132   - });
133   - this.mapResize$.observe(this.mapContainer.nativeElement);
  129 + import('@home/components/widget/lib/maps/map-widget2').then(
  130 + (mod) => {
  131 + this.mapWidget = new mod.MapWidgetController(MapProviders.openstreet, false, this.ctx, this.mapContainer.nativeElement);
  132 + this.mapResize$ = new ResizeObserver(() => {
  133 + this.mapWidget.resize();
  134 + });
  135 + this.mapResize$.observe(this.mapContainer.nativeElement);
  136 + }
  137 + );
134 138 }
135 139
136 140 ngOnDestroy() {
... ... @@ -142,8 +146,8 @@ export class TripAnimationComponent implements OnInit, AfterViewInit, OnDestroy
142 146 timeUpdated(time: number) {
143 147 this.currentTime = time;
144 148 const currentPosition = this.interpolatedTimeData
145   - .map(dataSource => dataSource[time])
146   - for(let j = 0; j < this.interpolatedTimeData.length; j++) {
  149 + .map(dataSource => dataSource[time]);
  150 + for (let j = 0; j < this.interpolatedTimeData.length; j++) {
147 151 if (isUndefined(currentPosition[j])) {
148 152 const timePoints = Object.keys(this.interpolatedTimeData[j]).map(item => parseInt(item, 10));
149 153 for (let i = 1; i < timePoints.length; i++) {
... ... @@ -155,13 +159,13 @@ export class TripAnimationComponent implements OnInit, AfterViewInit, OnDestroy
155 159 ...beforePosition,
156 160 time,
157 161 ...interpolateOnLineSegment(beforePosition, afterPosition, this.settings.latKeyName, this.settings.lngKeyName, ratio)
158   - }
  162 + };
159 163 break;
160 164 }
161 165 }
162 166 }
163 167 }
164   - for(let j = 0; j < this.interpolatedTimeData.length; j++) {
  168 + for (let j = 0; j < this.interpolatedTimeData.length; j++) {
165 169 if (isUndefined(currentPosition[j])) {
166 170 currentPosition[j] = this.calculateLastPoints(this.interpolatedTimeData[j], time);
167 171 }
... ... @@ -179,7 +183,7 @@ export class TripAnimationComponent implements OnInit, AfterViewInit, OnDestroy
179 183 }
180 184 this.mapWidget.map.updateMarkers(currentPosition, true, (trip) => {
181 185 this.activeTrip = trip;
182   - this.timeUpdated(this.currentTime)
  186 + this.timeUpdated(this.currentTime);
183 187 });
184 188 }
185 189 }
... ... @@ -187,14 +191,14 @@ export class TripAnimationComponent implements OnInit, AfterViewInit, OnDestroy
187 191 setActiveTrip() {
188 192 }
189 193
190   - private calculateLastPoints(dataSource: dataMap, time: number): FormattedData {
  194 + private calculateLastPoints(dataSource: DataMap, time: number): FormattedData {
191 195 const timeArr = Object.keys(dataSource);
192   - let index = timeArr.findIndex((dtime, index) => {
  196 + let index = timeArr.findIndex((dtime) => {
193 197 return Number(dtime) >= time;
194 198 });
195 199
196   - if(index !== -1) {
197   - if(Number(timeArr[index]) !== time && index !== 0) {
  200 + if (index !== -1) {
  201 + if (Number(timeArr[index]) !== time && index !== 0) {
198 202 index--;
199 203 }
200 204 } else {
... ... @@ -210,7 +214,7 @@ export class TripAnimationComponent implements OnInit, AfterViewInit, OnDestroy
210 214 this.maxTime = dataSource[dataSource.length - 1]?.time || -Infinity;
211 215 this.interpolatedTimeData[index] = this.interpolateArray(dataSource);
212 216 });
213   - if(!this.activeTrip){
  217 + if (!this.activeTrip) {
214 218 this.activeTrip = this.interpolatedTimeData.map(dataSource => dataSource[this.minTime]).filter(ds => ds)[0];
215 219 }
216 220 if (this.useAnchors) {
... ... @@ -230,7 +234,7 @@ export class TripAnimationComponent implements OnInit, AfterViewInit, OnDestroy
230 234
231 235 private calcMainTooltip(points: FormattedData[]): void {
232 236 const tooltips = [];
233   - for (let point of points) {
  237 + for (const point of points) {
234 238 tooltips.push(this.sanitizer.sanitize(SecurityContext.HTML, this.calcTooltip(point)));
235 239 }
236 240 this.mainTooltips = tooltips;
... ... @@ -259,7 +263,7 @@ export class TripAnimationComponent implements OnInit, AfterViewInit, OnDestroy
259 263 }
260 264 const timeStamp = Object.keys(result);
261 265 for (let i = 0; i < timeStamp.length - 1; i++) {
262   - result[timeStamp[i]].rotationAngle += findAngle(result[timeStamp[i]], result[timeStamp[i + 1]], latKeyName, lngKeyName)
  266 + result[timeStamp[i]].rotationAngle += findAngle(result[timeStamp[i]], result[timeStamp[i + 1]], latKeyName, lngKeyName);
263 267 }
264 268 return result;
265 269 }
... ...
... ... @@ -17,7 +17,7 @@
17 17 import { Inject, Injectable, Optional, Type } from '@angular/core';
18 18 import { DynamicComponentFactoryService } from '@core/services/dynamic-component-factory.service';
19 19 import { WidgetService } from '@core/http/widget.service';
20   -import { forkJoin, Observable, of, ReplaySubject, Subject, throwError } from 'rxjs';
  20 +import { forkJoin, from, Observable, of, ReplaySubject, Subject, throwError } from 'rxjs';
21 21 import {
22 22 ErrorWidgetType,
23 23 MissingWidgetType,
... ... @@ -30,7 +30,7 @@ import cssjs from '@core/css/css';
30 30 import { UtilsService } from '@core/services/utils.service';
31 31 import { ResourcesService } from '@core/services/resources.service';
32 32 import { Widget, widgetActionSources, WidgetControllerDescriptor, WidgetType } from '@shared/models/widget.models';
33   -import { catchError, map, mergeMap, switchMap } from 'rxjs/operators';
  33 +import { catchError, map, mergeMap, switchMap, tap } from 'rxjs/operators';
34 34 import { isFunction, isUndefined } from '@core/utils';
35 35 import { TranslateService } from '@ngx-translate/core';
36 36 import { DynamicWidgetComponent } from '@home/components/widget/dynamic-widget.component';
... ... @@ -42,6 +42,10 @@ import { WidgetTypeId } from '@app/shared/models/id/widget-type-id';
42 42 import { TenantId } from '@app/shared/models/id/tenant-id';
43 43 import { SharedModule } from '@shared/shared.module';
44 44 import { MODULES_MAP } from '@shared/public-api';
  45 +import * as tinycolor_ from 'tinycolor2';
  46 +import moment from 'moment';
  47 +
  48 +const tinycolor = tinycolor_;
45 49
46 50 // @dynamic
47 51 @Injectable()
... ... @@ -106,20 +110,77 @@ export class WidgetComponentService {
106 110 }
107 111 const initSubject = new ReplaySubject();
108 112 this.init$ = initSubject.asObservable();
109   - const loadDefaultWidgetInfoTasks = [
110   - this.loadWidgetResources(this.missingWidgetType, 'global-widget-missing-type', [SharedModule, WidgetComponentsModule]),
111   - this.loadWidgetResources(this.errorWidgetType, 'global-widget-error-type', [SharedModule, WidgetComponentsModule]),
112   - ];
113   - forkJoin(loadDefaultWidgetInfoTasks).subscribe(
  113 +
  114 + (window as any).tinycolor = tinycolor;
  115 + (window as any).cssjs = cssjs;
  116 + (window as any).moment = moment;
  117 +
  118 + const widgetModulesTasks: Observable<any>[] = [];
  119 + widgetModulesTasks.push(from(import('@home/components/widget/lib/flot-widget')).pipe(
  120 + tap((mod) => {
  121 + (window as any).TbFlot = mod.TbFlot;
  122 + }))
  123 + );
  124 + widgetModulesTasks.push(from(import('@home/components/widget/lib/analogue-compass')).pipe(
  125 + tap((mod) => {
  126 + (window as any).TbAnalogueCompass = mod.TbAnalogueCompass;
  127 + }))
  128 + );
  129 + widgetModulesTasks.push(from(import('@home/components/widget/lib/analogue-radial-gauge')).pipe(
  130 + tap((mod) => {
  131 + (window as any).TbAnalogueRadialGauge = mod.TbAnalogueRadialGauge;
  132 + }))
  133 + );
  134 + widgetModulesTasks.push(from(import('@home/components/widget/lib/analogue-linear-gauge')).pipe(
  135 + tap((mod) => {
  136 + (window as any).TbAnalogueLinearGauge = mod.TbAnalogueLinearGauge;
  137 + }))
  138 + );
  139 + widgetModulesTasks.push(from(import('@home/components/widget/lib/digital-gauge')).pipe(
  140 + tap((mod) => {
  141 + (window as any).TbCanvasDigitalGauge = mod.TbCanvasDigitalGauge;
  142 + }))
  143 + );
  144 + widgetModulesTasks.push(from(import('@home/components/widget/lib/maps/map-widget2')).pipe(
  145 + tap((mod) => {
  146 + (window as any).TbMapWidgetV2 = mod.TbMapWidgetV2;
  147 + }))
  148 + );
  149 + widgetModulesTasks.push(from(import('@home/components/widget/trip-animation/trip-animation.component')).pipe(
  150 + tap((mod) => {
  151 + (window as any).TbTripAnimationWidget = mod.TbTripAnimationWidget;
  152 + }))
  153 + );
  154 +
  155 + forkJoin(widgetModulesTasks).subscribe(
114 156 () => {
115   - initSubject.next();
  157 + const loadDefaultWidgetInfoTasks = [
  158 + this.loadWidgetResources(this.missingWidgetType, 'global-widget-missing-type', [SharedModule, WidgetComponentsModule]),
  159 + this.loadWidgetResources(this.errorWidgetType, 'global-widget-error-type', [SharedModule, WidgetComponentsModule]),
  160 + ];
  161 + forkJoin(loadDefaultWidgetInfoTasks).subscribe(
  162 + () => {
  163 + initSubject.next();
  164 + },
  165 + (e) => {
  166 + let errorMessages = ['Failed to load default widget types!'];
  167 + if (e && e.length) {
  168 + errorMessages = errorMessages.concat(e);
  169 + }
  170 + console.error('Failed to load default widget types!');
  171 + initSubject.error({
  172 + widgetInfo: this.errorWidgetType,
  173 + errorMessages
  174 + });
  175 + }
  176 + );
116 177 },
117 178 (e) => {
118   - let errorMessages = ['Failed to load default widget types!'];
  179 + let errorMessages = ['Failed to load widget modules!'];
119 180 if (e && e.length) {
120 181 errorMessages = errorMessages.concat(e);
121 182 }
122   - console.error('Failed to load default widget types!');
  183 + console.error('Failed to load widget modules!');
123 184 initSubject.error({
124 185 widgetInfo: this.errorWidgetType,
125 186 errorMessages
... ...
... ... @@ -32,7 +32,8 @@ import { NULL_UUID } from '@shared/models/id/has-uuid';
32 32 import { Hotkey } from 'angular2-hotkeys';
33 33 import { TranslateService } from '@ngx-translate/core';
34 34 import { getCurrentIsLoading } from '@app/core/interceptors/load.selectors';
35   -import * as ace from 'ace-builds';
  35 +import { Ace } from 'ace-builds';
  36 +import { getAce, Range } from '@shared/models/ace/ace.models';
36 37 import { css_beautify, html_beautify } from 'js-beautify';
37 38 import { CancelAnimationFrame, RafService } from '@core/services/raf.service';
38 39 import { WINDOW } from '@core/services/window.service';
... ... @@ -44,10 +45,12 @@ import {
44 45 SaveWidgetTypeAsDialogComponent,
45 46 SaveWidgetTypeAsDialogResult
46 47 } from '@home/pages/widget/save-widget-type-as-dialog.component';
47   -import { Subscription } from 'rxjs';
  48 +import { forkJoin, from, Subscription } from 'rxjs';
48 49 import { ResizeObserver } from '@juggle/resize-observer';
49 50 import Timeout = NodeJS.Timeout;
50 51 import { widgetEditorCompleter } from '@home/pages/widget/widget-editor.models';
  52 +import { Observable } from 'rxjs/internal/Observable';
  53 +import { map, tap } from 'rxjs/operators';
51 54
52 55 // @dynamic
53 56 @Component({
... ... @@ -119,13 +122,13 @@ export class WidgetEditorComponent extends PageComponent implements OnInit, OnDe
119 122 javascriptFullscreen = false;
120 123 iFrameFullscreen = false;
121 124
122   - aceEditors: ace.Ace.Editor[] = [];
  125 + aceEditors: Ace.Editor[] = [];
123 126 editorsResizeCafs: {[editorId: string]: CancelAnimationFrame} = {};
124   - htmlEditor: ace.Ace.Editor;
125   - cssEditor: ace.Ace.Editor;
126   - jsonSettingsEditor: ace.Ace.Editor;
127   - dataKeyJsonSettingsEditor: ace.Ace.Editor;
128   - jsEditor: ace.Ace.Editor;
  127 + htmlEditor: Ace.Editor;
  128 + cssEditor: Ace.Editor;
  129 + jsonSettingsEditor: Ace.Editor;
  130 + dataKeyJsonSettingsEditor: Ace.Editor;
  131 + jsEditor: Ace.Editor;
129 132 aceResize$: ResizeObserver;
130 133
131 134 onWindowMessageListener = this.onWindowMessage.bind(this);
... ... @@ -277,51 +280,85 @@ export class WidgetEditorComponent extends PageComponent implements OnInit, OnDe
277 280 this.onAceEditorResize(editor);
278 281 });
279 282 });
280   - this.htmlEditor = this.createAceEditor(this.htmlInputElmRef, 'html');
281   - this.htmlEditor.on('input', () => {
282   - const editorValue = this.htmlEditor.getValue();
283   - if (this.widget.templateHtml !== editorValue) {
284   - this.widget.templateHtml = editorValue;
285   - this.isDirty = true;
286   - }
287   - });
288   - this.cssEditor = this.createAceEditor(this.cssInputElmRef, 'css');
289   - this.cssEditor.on('input', () => {
290   - const editorValue = this.cssEditor.getValue();
291   - if (this.widget.templateCss !== editorValue) {
292   - this.widget.templateCss = editorValue;
293   - this.isDirty = true;
294   - }
295   - });
296   - this.jsonSettingsEditor = this.createAceEditor(this.settingsJsonInputElmRef, 'json');
297   - this.jsonSettingsEditor.on('input', () => {
298   - const editorValue = this.jsonSettingsEditor.getValue();
299   - if (this.widget.settingsSchema !== editorValue) {
300   - this.widget.settingsSchema = editorValue;
301   - this.isDirty = true;
302   - }
303   - });
304   - this.dataKeyJsonSettingsEditor = this.createAceEditor(this.dataKeySettingsJsonInputElmRef, 'json');
305   - this.dataKeyJsonSettingsEditor.on('input', () => {
306   - const editorValue = this.dataKeyJsonSettingsEditor.getValue();
307   - if (this.widget.dataKeySettingsSchema !== editorValue) {
308   - this.widget.dataKeySettingsSchema = editorValue;
309   - this.isDirty = true;
310   - }
311   - });
312   - this.jsEditor = this.createAceEditor(this.javascriptInputElmRef, 'javascript');
313   - this.jsEditor.on('input', () => {
314   - const editorValue = this.jsEditor.getValue();
315   - if (this.widget.controllerScript !== editorValue) {
316   - this.widget.controllerScript = editorValue;
317   - this.isDirty = true;
  283 +
  284 + const editorsObservables: Observable<any>[] = [];
  285 +
  286 +
  287 + editorsObservables.push(this.createAceEditor(this.htmlInputElmRef, 'html').pipe(
  288 + tap((editor) => {
  289 + this.htmlEditor = editor;
  290 + this.htmlEditor.on('input', () => {
  291 + const editorValue = this.htmlEditor.getValue();
  292 + if (this.widget.templateHtml !== editorValue) {
  293 + this.widget.templateHtml = editorValue;
  294 + this.isDirty = true;
  295 + }
  296 + });
  297 + })
  298 + ));
  299 +
  300 + editorsObservables.push(this.createAceEditor(this.cssInputElmRef, 'css').pipe(
  301 + tap((editor) => {
  302 + this.cssEditor = editor;
  303 + this.cssEditor.on('input', () => {
  304 + const editorValue = this.cssEditor.getValue();
  305 + if (this.widget.templateCss !== editorValue) {
  306 + this.widget.templateCss = editorValue;
  307 + this.isDirty = true;
  308 + }
  309 + });
  310 + })
  311 + ));
  312 +
  313 + editorsObservables.push(this.createAceEditor(this.settingsJsonInputElmRef, 'json').pipe(
  314 + tap((editor) => {
  315 + this.jsonSettingsEditor = editor;
  316 + this.jsonSettingsEditor.on('input', () => {
  317 + const editorValue = this.jsonSettingsEditor.getValue();
  318 + if (this.widget.settingsSchema !== editorValue) {
  319 + this.widget.settingsSchema = editorValue;
  320 + this.isDirty = true;
  321 + }
  322 + });
  323 + })
  324 + ));
  325 +
  326 + editorsObservables.push(this.createAceEditor(this.dataKeySettingsJsonInputElmRef, 'json').pipe(
  327 + tap((editor) => {
  328 + this.dataKeyJsonSettingsEditor = editor;
  329 + this.dataKeyJsonSettingsEditor.on('input', () => {
  330 + const editorValue = this.dataKeyJsonSettingsEditor.getValue();
  331 + if (this.widget.dataKeySettingsSchema !== editorValue) {
  332 + this.widget.dataKeySettingsSchema = editorValue;
  333 + this.isDirty = true;
  334 + }
  335 + });
  336 + })
  337 + ));
  338 +
  339 + editorsObservables.push(this.createAceEditor(this.javascriptInputElmRef, 'javascript').pipe(
  340 + tap((editor) => {
  341 + this.jsEditor = editor;
  342 + this.jsEditor.on('input', () => {
  343 + const editorValue = this.jsEditor.getValue();
  344 + if (this.widget.controllerScript !== editorValue) {
  345 + this.widget.controllerScript = editorValue;
  346 + this.isDirty = true;
  347 + }
  348 + });
  349 + this.jsEditor.on('change', () => {
  350 + this.cleanupJsErrors();
  351 + });
  352 + this.jsEditor.completers = [widgetEditorCompleter, ...(this.jsEditor.completers || [])];
  353 + })
  354 + ));
  355 +
  356 + forkJoin(editorsObservables).subscribe(
  357 + () => {
  358 + this.setAceEditorValues();
318 359 }
319   - });
320   - this.jsEditor.on('change', () => {
321   - this.cleanupJsErrors();
322   - });
323   - this.jsEditor.completers = [widgetEditorCompleter, ...(this.jsEditor.completers || [])];
324   - this.setAceEditorValues();
  360 + );
  361 +
325 362 }
326 363
327 364 private setAceEditorValues() {
... ... @@ -332,9 +369,9 @@ export class WidgetEditorComponent extends PageComponent implements OnInit, OnDe
332 369 this.jsEditor.setValue(this.widget.controllerScript ? this.widget.controllerScript : '', -1);
333 370 }
334 371
335   - private createAceEditor(editorElementRef: ElementRef, mode: string): ace.Ace.Editor {
  372 + private createAceEditor(editorElementRef: ElementRef, mode: string): Observable<Ace.Editor> {
336 373 const editorElement = editorElementRef.nativeElement;
337   - let editorOptions: Partial<ace.Ace.EditorOptions> = {
  374 + let editorOptions: Partial<Ace.EditorOptions> = {
338 375 mode: `ace/mode/${mode}`,
339 376 showGutter: true,
340 377 showPrintMargin: true
... ... @@ -345,14 +382,18 @@ export class WidgetEditorComponent extends PageComponent implements OnInit, OnDe
345 382 enableLiveAutocompletion: true
346 383 };
347 384 editorOptions = {...editorOptions, ...advancedOptions};
348   - const aceEditor = ace.edit(editorElement, editorOptions);
349   - aceEditor.session.setUseWrapMode(true);
350   - this.aceEditors.push(aceEditor);
351   - this.aceResize$.observe(editorElement);
352   - return aceEditor;
  385 + return getAce().pipe(
  386 + map((ace) => {
  387 + const aceEditor = ace.edit(editorElement, editorOptions);
  388 + aceEditor.session.setUseWrapMode(true);
  389 + this.aceEditors.push(aceEditor);
  390 + this.aceResize$.observe(editorElement);
  391 + return aceEditor;
  392 + })
  393 + );
353 394 }
354 395
355   - private onAceEditorResize(aceEditor: ace.Ace.Editor) {
  396 + private onAceEditorResize(aceEditor: Ace.Editor) {
356 397 if (this.editorsResizeCafs[aceEditor.id]) {
357 398 this.editorsResizeCafs[aceEditor.id]();
358 399 delete this.editorsResizeCafs[aceEditor.id];
... ... @@ -443,11 +484,11 @@ export class WidgetEditorComponent extends PageComponent implements OnInit, OnDe
443 484 if (details.columnNumber) {
444 485 column = details.columnNumber;
445 486 }
446   - const errorMarkerId = this.jsEditor.session.addMarker(new ace.Range(line, 0, line, Infinity),
  487 + const errorMarkerId = this.jsEditor.session.addMarker(new Range(line, 0, line, Infinity),
447 488 'ace_active-line', 'screenLine');
448 489 this.errorMarkers.push(errorMarkerId);
449 490 const annotations = this.jsEditor.session.getAnnotations();
450   - const errorAnnotation: ace.Ace.Annotation = {
  491 + const errorAnnotation: Ace.Annotation = {
451 492 row: line,
452 493 column,
453 494 text: details.message,
... ...
... ... @@ -25,7 +25,8 @@ import {
25 25 ViewEncapsulation
26 26 } from '@angular/core';
27 27 import { ControlValueAccessor, FormControl, NG_VALIDATORS, NG_VALUE_ACCESSOR, Validator } from '@angular/forms';
28   -import * as ace from 'ace-builds';
  28 +import { Ace } from 'ace-builds';
  29 +import { getAce, Range } from '@shared/models/ace/ace.models';
29 30 import { coerceBooleanProperty } from '@angular/cdk/coercion';
30 31 import { ActionNotificationHide, ActionNotificationShow } from '@core/notification/notification.actions';
31 32 import { Store } from '@ngrx/store';
... ... @@ -60,7 +61,7 @@ export class JsFuncComponent implements OnInit, OnDestroy, ControlValueAccessor,
60 61 @ViewChild('javascriptEditor', {static: true})
61 62 javascriptEditorElmRef: ElementRef;
62 63
63   - private jsEditor: ace.Ace.Editor;
  64 + private jsEditor: Ace.Editor;
64 65 private editorsResizeCaf: CancelAnimationFrame;
65 66 private editorResize$: ResizeObserver;
66 67 private ignoreChange = false;
... ... @@ -136,7 +137,7 @@ export class JsFuncComponent implements OnInit, OnDestroy, ControlValueAccessor,
136 137 });
137 138 }
138 139 const editorElement = this.javascriptEditorElmRef.nativeElement;
139   - let editorOptions: Partial<ace.Ace.EditorOptions> = {
  140 + let editorOptions: Partial<Ace.EditorOptions> = {
140 141 mode: 'ace/mode/javascript',
141 142 showGutter: true,
142 143 showPrintMargin: true,
... ... @@ -150,22 +151,27 @@ export class JsFuncComponent implements OnInit, OnDestroy, ControlValueAccessor,
150 151 };
151 152
152 153 editorOptions = {...editorOptions, ...advancedOptions};
153   - this.jsEditor = ace.edit(editorElement, editorOptions);
154   - this.jsEditor.session.setUseWrapMode(true);
155   - this.jsEditor.setValue(this.modelValue ? this.modelValue : '', -1);
156   - this.jsEditor.on('change', () => {
157   - if (!this.ignoreChange) {
158   - this.cleanupJsErrors();
159   - this.updateView();
  154 + getAce().subscribe(
  155 + (ace) => {
  156 + this.jsEditor = ace.edit(editorElement, editorOptions);
  157 + this.jsEditor.session.setUseWrapMode(true);
  158 + this.jsEditor.setValue(this.modelValue ? this.modelValue : '', -1);
  159 + this.jsEditor.setReadOnly(this.disabled);
  160 + this.jsEditor.on('change', () => {
  161 + if (!this.ignoreChange) {
  162 + this.cleanupJsErrors();
  163 + this.updateView();
  164 + }
  165 + });
  166 + if (this.editorCompleter) {
  167 + this.jsEditor.completers = [this.editorCompleter, ...(this.jsEditor.completers || [])];
  168 + }
  169 + this.editorResize$ = new ResizeObserver(() => {
  170 + this.onAceEditorResize();
  171 + });
  172 + this.editorResize$.observe(editorElement);
160 173 }
161   - });
162   - if (this.editorCompleter) {
163   - this.jsEditor.completers = [this.editorCompleter, ...(this.jsEditor.completers || [])];
164   - }
165   - this.editorResize$ = new ResizeObserver(() => {
166   - this.onAceEditorResize();
167   - });
168   - this.editorResize$.observe(editorElement);
  174 + );
169 175 }
170 176
171 177 ngOnDestroy(): void {
... ... @@ -294,11 +300,11 @@ export class JsFuncComponent implements OnInit, OnDestroy, ControlValueAccessor,
294 300 if (details.columnNumber) {
295 301 column = details.columnNumber;
296 302 }
297   - const errorMarkerId = this.jsEditor.session.addMarker(new ace.Range(line, 0, line, Infinity),
  303 + const errorMarkerId = this.jsEditor.session.addMarker(new Range(line, 0, line, Infinity),
298 304 'ace_active-line', 'screenLine');
299 305 this.errorMarkers.push(errorMarkerId);
300 306 const annotations = this.jsEditor.session.getAnnotations();
301   - const errorAnnotation: ace.Ace.Annotation = {
  307 + const errorAnnotation: Ace.Annotation = {
302 308 row: line,
303 309 column,
304 310 text: details.message,
... ...
... ... @@ -26,7 +26,7 @@ import {
26 26 ViewChild
27 27 } from '@angular/core';
28 28 import { ControlValueAccessor, FormControl, NG_VALIDATORS, NG_VALUE_ACCESSOR, Validator } from '@angular/forms';
29   -import * as ace from 'ace-builds';
  29 +import { Ace } from 'ace-builds';
30 30 import { coerceBooleanProperty } from '@angular/cdk/coercion';
31 31 import { ActionNotificationHide, ActionNotificationShow } from '@core/notification/notification.actions';
32 32 import { Store } from '@ngrx/store';
... ... @@ -35,6 +35,7 @@ import { ContentType, contentTypesMap } from '@shared/models/constants';
35 35 import { CancelAnimationFrame, RafService } from '@core/services/raf.service';
36 36 import { guid } from '@core/utils';
37 37 import { ResizeObserver } from '@juggle/resize-observer';
  38 +import { getAce } from '@shared/models/ace/ace.models';
38 39
39 40 @Component({
40 41 selector: 'tb-json-content',
... ... @@ -58,7 +59,7 @@ export class JsonContentComponent implements OnInit, ControlValueAccessor, Valid
58 59 @ViewChild('jsonEditor', {static: true})
59 60 jsonEditorElmRef: ElementRef;
60 61
61   - private jsonEditor: ace.Ace.Editor;
  62 + private jsonEditor: Ace.Editor;
62 63 private editorsResizeCaf: CancelAnimationFrame;
63 64 private editorResize$: ResizeObserver;
64 65 private ignoreChange = false;
... ... @@ -123,7 +124,7 @@ export class JsonContentComponent implements OnInit, ControlValueAccessor, Valid
123 124 if (this.contentType) {
124 125 mode = contentTypesMap.get(this.contentType).code;
125 126 }
126   - let editorOptions: Partial<ace.Ace.EditorOptions> = {
  127 + let editorOptions: Partial<Ace.EditorOptions> = {
127 128 mode: `ace/mode/${mode}`,
128 129 showGutter: true,
129 130 showPrintMargin: false,
... ... @@ -137,22 +138,27 @@ export class JsonContentComponent implements OnInit, ControlValueAccessor, Valid
137 138 };
138 139
139 140 editorOptions = {...editorOptions, ...advancedOptions};
140   - this.jsonEditor = ace.edit(editorElement, editorOptions);
141   - this.jsonEditor.session.setUseWrapMode(true);
142   - this.jsonEditor.setValue(this.contentBody ? this.contentBody : '', -1);
143   - this.jsonEditor.on('change', () => {
144   - if (!this.ignoreChange) {
145   - this.cleanupJsonErrors();
146   - this.updateView();
  141 + getAce().subscribe(
  142 + (ace) => {
  143 + this.jsonEditor = ace.edit(editorElement, editorOptions);
  144 + this.jsonEditor.session.setUseWrapMode(true);
  145 + this.jsonEditor.setValue(this.contentBody ? this.contentBody : '', -1);
  146 + this.jsonEditor.setReadOnly(this.disabled || this.readonly);
  147 + this.jsonEditor.on('change', () => {
  148 + if (!this.ignoreChange) {
  149 + this.cleanupJsonErrors();
  150 + this.updateView();
  151 + }
  152 + });
  153 + this.jsonEditor.on('blur', () => {
  154 + this.contentValid = !this.validateContent || this.doValidate(true);
  155 + });
  156 + this.editorResize$ = new ResizeObserver(() => {
  157 + this.onAceEditorResize();
  158 + });
  159 + this.editorResize$.observe(editorElement);
147 160 }
148   - });
149   - this.jsonEditor.on('blur', () => {
150   - this.contentValid = !this.validateContent || this.doValidate(true);
151   - });
152   - this.editorResize$ = new ResizeObserver(() => {
153   - this.onAceEditorResize();
154   - });
155   - this.editorResize$.observe(editorElement);
  161 + );
156 162 }
157 163
158 164 ngOnDestroy(): void {
... ...
... ... @@ -16,10 +16,20 @@
16 16 import * as React from 'react';
17 17 import ThingsboardBaseComponent from './json-form-base-component';
18 18 import reactCSS from 'reactcss';
19   -import ReactAce from 'react-ace';
20 19 import Button from '@material-ui/core/Button';
21 20 import { JsonFormFieldProps, JsonFormFieldState } from '@shared/components/json-form/react/json-form.models';
22 21 import { IEditorProps } from 'react-ace/src/types';
  22 +import { map, mergeMap } from 'rxjs/operators';
  23 +import { loadAceDependencies } from '@shared/models/ace/ace.models';
  24 +import { from } from 'rxjs';
  25 +
  26 +const ReactAce = React.lazy(() => {
  27 + return loadAceDependencies().pipe(
  28 + mergeMap(() => {
  29 + return from(import('react-ace'));
  30 + })
  31 + ).toPromise();
  32 +});
23 33
24 34 interface ThingsboardAceEditorProps extends JsonFormFieldProps {
25 35 mode: string;
... ... @@ -153,22 +163,24 @@ class ThingsboardAceEditor extends React.Component<ThingsboardAceEditorProps, Th
153 163 'Exit fullscreen' : 'Fullscreen'}
154 164 </Button>
155 165 </div>
156   - <ReactAce mode={this.props.mode}
157   - height={this.state.isFull ? '100%' : '150px'}
158   - width={this.state.isFull ? '100%' : '300px'}
159   - theme='github'
160   - onChange={this.onValueChanged}
161   - onFocus={this.onFocus}
162   - onBlur={this.onBlur}
163   - onLoad={this.onLoad}
164   - name={this.props.form.title}
165   - value={this.state.value}
166   - readOnly={this.props.form.readonly}
167   - editorProps={{$blockScrolling: Infinity}}
168   - enableBasicAutocompletion={true}
169   - enableSnippets={true}
170   - enableLiveAutocompletion={true}
171   - style={style}/>
  166 + <React.Suspense fallback={<div>Loading...</div>}>
  167 + <ReactAce mode={this.props.mode}
  168 + height={this.state.isFull ? '100%' : '150px'}
  169 + width={this.state.isFull ? '100%' : '300px'}
  170 + theme='github'
  171 + onChange={this.onValueChanged}
  172 + onFocus={this.onFocus}
  173 + onBlur={this.onBlur}
  174 + onLoad={this.onLoad}
  175 + name={this.props.form.title}
  176 + value={this.state.value}
  177 + readOnly={this.props.form.readonly}
  178 + editorProps={{$blockScrolling: Infinity}}
  179 + enableBasicAutocompletion={true}
  180 + enableSnippets={true}
  181 + enableLiveAutocompletion={true}
  182 + style={style}/>
  183 + </React.Suspense>
172 184 </div>
173 185 <div className='json-form-error'
174 186 style={{opacity: this.props.valid ? '0' : '1'}}>{this.props.error}</div>
... ...
... ... @@ -16,7 +16,7 @@
16 16
17 17 import { Component, ElementRef, forwardRef, Input, OnDestroy, OnInit, ViewChild } from '@angular/core';
18 18 import { ControlValueAccessor, FormControl, NG_VALIDATORS, NG_VALUE_ACCESSOR, Validator } from '@angular/forms';
19   -import * as ace from 'ace-builds';
  19 +import { Ace } from 'ace-builds';
20 20 import { coerceBooleanProperty } from '@angular/cdk/coercion';
21 21 import { ActionNotificationHide, ActionNotificationShow } from '@core/notification/notification.actions';
22 22 import { Store } from '@ngrx/store';
... ... @@ -24,6 +24,7 @@ import { AppState } from '@core/core.state';
24 24 import { CancelAnimationFrame, RafService } from '@core/services/raf.service';
25 25 import { guid } from '@core/utils';
26 26 import { ResizeObserver } from '@juggle/resize-observer';
  27 +import { getAce } from '@shared/models/ace/ace.models';
27 28
28 29 @Component({
29 30 selector: 'tb-json-object-edit',
... ... @@ -47,7 +48,7 @@ export class JsonObjectEditComponent implements OnInit, ControlValueAccessor, Va
47 48 @ViewChild('jsonEditor', {static: true})
48 49 jsonEditorElmRef: ElementRef;
49 50
50   - private jsonEditor: ace.Ace.Editor;
  51 + private jsonEditor: Ace.Editor;
51 52 private editorsResizeCaf: CancelAnimationFrame;
52 53 private editorResize$: ResizeObserver;
53 54
... ... @@ -102,7 +103,7 @@ export class JsonObjectEditComponent implements OnInit, ControlValueAccessor, Va
102 103
103 104 ngOnInit(): void {
104 105 const editorElement = this.jsonEditorElmRef.nativeElement;
105   - let editorOptions: Partial<ace.Ace.EditorOptions> = {
  106 + let editorOptions: Partial<Ace.EditorOptions> = {
106 107 mode: 'ace/mode/json',
107 108 showGutter: true,
108 109 showPrintMargin: false,
... ... @@ -116,19 +117,24 @@ export class JsonObjectEditComponent implements OnInit, ControlValueAccessor, Va
116 117 };
117 118
118 119 editorOptions = {...editorOptions, ...advancedOptions};
119   - this.jsonEditor = ace.edit(editorElement, editorOptions);
120   - this.jsonEditor.session.setUseWrapMode(false);
121   - this.jsonEditor.setValue(this.contentValue ? this.contentValue : '', -1);
122   - this.jsonEditor.on('change', () => {
123   - if (!this.ignoreChange) {
124   - this.cleanupJsonErrors();
125   - this.updateView();
  120 + getAce().subscribe(
  121 + (ace) => {
  122 + this.jsonEditor = ace.edit(editorElement, editorOptions);
  123 + this.jsonEditor.session.setUseWrapMode(false);
  124 + this.jsonEditor.setValue(this.contentValue ? this.contentValue : '', -1);
  125 + this.jsonEditor.setReadOnly(this.disabled || this.readonly);
  126 + this.jsonEditor.on('change', () => {
  127 + if (!this.ignoreChange) {
  128 + this.cleanupJsonErrors();
  129 + this.updateView();
  130 + }
  131 + });
  132 + this.editorResize$ = new ResizeObserver(() => {
  133 + this.onAceEditorResize();
  134 + });
  135 + this.editorResize$.observe(editorElement);
126 136 }
127   - });
128   - this.editorResize$ = new ResizeObserver(() => {
129   - this.onAceEditorResize();
130   - });
131   - this.editorResize$.observe(editorElement);
  137 + );
132 138 }
133 139
134 140 ngOnDestroy(): void {
... ...
  1 +///
  2 +/// Copyright © 2016-2020 The Thingsboard Authors
  3 +///
  4 +/// Licensed under the Apache License, Version 2.0 (the "License");
  5 +/// you may not use this file except in compliance with the License.
  6 +/// You may obtain a copy of the License at
  7 +///
  8 +/// http://www.apache.org/licenses/LICENSE-2.0
  9 +///
  10 +/// Unless required by applicable law or agreed to in writing, software
  11 +/// distributed under the License is distributed on an "AS IS" BASIS,
  12 +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 +/// See the License for the specific language governing permissions and
  14 +/// limitations under the License.
  15 +///
  16 +
  17 +import { Ace } from 'ace-builds';
  18 +import { Observable } from 'rxjs/internal/Observable';
  19 +import { forkJoin, from, of } from 'rxjs';
  20 +import { map, mergeMap, tap } from 'rxjs/operators';
  21 +
  22 +let aceDependenciesLoaded = false;
  23 +let aceModule: any;
  24 +
  25 +export function loadAceDependencies(): Observable<any> {
  26 + if (aceDependenciesLoaded) {
  27 + return of(null);
  28 + } else {
  29 + const aceObservables: Observable<any>[] = [];
  30 + aceObservables.push(from(import('ace-builds/src-noconflict/ext-language_tools')));
  31 + aceObservables.push(from(import('ace-builds/src-noconflict/ext-searchbox')));
  32 + aceObservables.push(from(import('ace-builds/src-noconflict/mode-java')));
  33 + aceObservables.push(from(import('ace-builds/src-noconflict/mode-css')));
  34 + aceObservables.push(from(import('ace-builds/src-noconflict/mode-json')));
  35 + aceObservables.push(from(import('ace-builds/src-noconflict/mode-javascript')));
  36 + aceObservables.push(from(import('ace-builds/src-noconflict/mode-text')));
  37 + aceObservables.push(from(import('ace-builds/src-noconflict/mode-markdown')));
  38 + aceObservables.push(from(import('ace-builds/src-noconflict/mode-html')));
  39 + aceObservables.push(from(import('ace-builds/src-noconflict/snippets/java')));
  40 + aceObservables.push(from(import('ace-builds/src-noconflict/snippets/css')));
  41 + aceObservables.push(from(import('ace-builds/src-noconflict/snippets/json')));
  42 + aceObservables.push(from(import('ace-builds/src-noconflict/snippets/javascript')));
  43 + aceObservables.push(from(import('ace-builds/src-noconflict/snippets/text')));
  44 + aceObservables.push(from(import('ace-builds/src-noconflict/snippets/markdown')));
  45 + aceObservables.push(from(import('ace-builds/src-noconflict/snippets/html')));
  46 + aceObservables.push(from(import('ace-builds/src-noconflict/theme-github')));
  47 + return forkJoin(aceObservables).pipe(
  48 + tap(() => {
  49 + aceDependenciesLoaded = true;
  50 + })
  51 + );
  52 + }
  53 +}
  54 +
  55 +export function getAce(): Observable<any> {
  56 + if (aceModule) {
  57 + return of(aceModule);
  58 + } else {
  59 + return from(import('ace')).pipe(
  60 + mergeMap((module) => {
  61 + return loadAceDependencies().pipe(
  62 + map(() => module)
  63 + );
  64 + }),
  65 + tap((module) => {
  66 + aceModule = module;
  67 + })
  68 + );
  69 + }
  70 +}
  71 +
  72 +export class Range implements Ace.Range {
  73 +
  74 + public start: Ace.Point;
  75 + public end: Ace.Point;
  76 +
  77 + constructor(startRow: number, startColumn: number, endRow: number, endColumn: number) {
  78 + this.start = {
  79 + row: startRow,
  80 + column: startColumn
  81 + };
  82 +
  83 + this.end = {
  84 + row: endRow,
  85 + column: endColumn
  86 + };
  87 + }
  88 +
  89 + static fromPoints(start: Ace.Point, end: Ace.Point): Ace.Range {
  90 + return new Range(start.row, start.column, end.row, end.column);
  91 + }
  92 +
  93 + clipRows(firstRow: number, lastRow: number): Ace.Range {
  94 + let end: Ace.Point;
  95 + let start: Ace.Point;
  96 + if (this.end.row > lastRow) {
  97 + end = {row: lastRow + 1, column: 0};
  98 + } else if (this.end.row < firstRow) {
  99 + end = {row: firstRow, column: 0};
  100 + }
  101 +
  102 + if (this.start.row > lastRow) {
  103 + start = {row: lastRow + 1, column: 0};
  104 + } else if (this.start.row < firstRow) {
  105 + start = {row: firstRow, column: 0};
  106 + }
  107 + return Range.fromPoints(start || this.start, end || this.end);
  108 + }
  109 +
  110 + clone(): Ace.Range {
  111 + return Range.fromPoints(this.start, this.end);
  112 + }
  113 +
  114 + collapseRows(): Ace.Range {
  115 + if (this.end.column === 0) {
  116 + return new Range(this.start.row, 0, Math.max(this.start.row, this.end.row - 1), 0);
  117 + } else {
  118 + return new Range(this.start.row, 0, this.end.row, 0);
  119 + }
  120 + }
  121 +
  122 + compare(row: number, column: number): number {
  123 + if (!this.isMultiLine()) {
  124 + if (row === this.start.row) {
  125 + return column < this.start.column ? -1 : (column > this.end.column ? 1 : 0);
  126 + }
  127 + }
  128 +
  129 + if (row < this.start.row) {
  130 + return -1;
  131 + }
  132 +
  133 + if (row > this.end.row) {
  134 + return 1;
  135 + }
  136 +
  137 + if (this.start.row === row) {
  138 + return column >= this.start.column ? 0 : -1;
  139 + }
  140 +
  141 + if (this.end.row === row) {
  142 + return column <= this.end.column ? 0 : 1;
  143 + }
  144 +
  145 + return 0;
  146 + }
  147 +
  148 + compareEnd(row: number, column: number): number {
  149 + if (this.end.row === row && this.end.column === column) {
  150 + return 1;
  151 + } else {
  152 + return this.compare(row, column);
  153 + }
  154 + }
  155 +
  156 + compareInside(row: number, column: number): number {
  157 + if (this.end.row === row && this.end.column === column) {
  158 + return 1;
  159 + } else if (this.start.row === row && this.start.column === column) {
  160 + return -1;
  161 + } else {
  162 + return this.compare(row, column);
  163 + }
  164 + }
  165 +
  166 + comparePoint(p: Ace.Point): number {
  167 + return this.compare(p.row, p.column);
  168 + }
  169 +
  170 + compareRange(range: Ace.Range): number {
  171 + let cmp: number;
  172 + const end = range.end;
  173 + const start = range.start;
  174 +
  175 + cmp = this.compare(end.row, end.column);
  176 + if (cmp === 1) {
  177 + cmp = this.compare(start.row, start.column);
  178 + if (cmp === 1) {
  179 + return 2;
  180 + } else if (cmp === 0) {
  181 + return 1;
  182 + } else {
  183 + return 0;
  184 + }
  185 + } else if (cmp === -1) {
  186 + return -2;
  187 + } else {
  188 + cmp = this.compare(start.row, start.column);
  189 + if (cmp === -1) {
  190 + return -1;
  191 + } else if (cmp === 1) {
  192 + return 42;
  193 + } else {
  194 + return 0;
  195 + }
  196 + }
  197 + }
  198 +
  199 + compareStart(row: number, column: number): number {
  200 + if (this.start.row === row && this.start.column === column) {
  201 + return -1;
  202 + } else {
  203 + return this.compare(row, column);
  204 + }
  205 + }
  206 +
  207 + contains(row: number, column: number): boolean {
  208 + return this.compare(row, column) === 0;
  209 + }
  210 +
  211 + containsRange(range: Ace.Range): boolean {
  212 + return this.comparePoint(range.start) === 0 && this.comparePoint(range.end) === 0;
  213 + }
  214 +
  215 + extend(row: number, column: number): Ace.Range {
  216 + const cmp = this.compare(row, column);
  217 + let end: Ace.Point;
  218 + let start: Ace.Point;
  219 + if (cmp === 0) {
  220 + return this;
  221 + } else if (cmp === -1) {
  222 + start = {row, column};
  223 + } else {
  224 + end = {row, column};
  225 + }
  226 + return Range.fromPoints(start || this.start, end || this.end);
  227 + }
  228 +
  229 + inside(row: number, column: number): boolean {
  230 + if (this.compare(row, column) === 0) {
  231 + if (this.isEnd(row, column) || this.isStart(row, column)) {
  232 + return false;
  233 + } else {
  234 + return true;
  235 + }
  236 + }
  237 + return false;
  238 + }
  239 +
  240 + insideEnd(row: number, column: number): boolean {
  241 + if (this.compare(row, column) === 0) {
  242 + if (this.isStart(row, column)) {
  243 + return false;
  244 + } else {
  245 + return true;
  246 + }
  247 + }
  248 + return false;
  249 + }
  250 +
  251 + insideStart(row: number, column: number): boolean {
  252 + if (this.compare(row, column) === 0) {
  253 + if (this.isEnd(row, column)) {
  254 + return false;
  255 + } else {
  256 + return true;
  257 + }
  258 + }
  259 + return false;
  260 + }
  261 +
  262 + intersects(range: Ace.Range): boolean {
  263 + const cmp = this.compareRange(range);
  264 + return (cmp === -1 || cmp === 0 || cmp === 1);
  265 + }
  266 +
  267 + isEmpty(): boolean {
  268 + return (this.start.row === this.end.row && this.start.column === this.end.column);
  269 + }
  270 +
  271 + isEnd(row: number, column: number): boolean {
  272 + return this.end.row === row && this.end.column === column;
  273 + }
  274 +
  275 + isEqual(range: Ace.Range): boolean {
  276 + return this.start.row === range.start.row &&
  277 + this.end.row === range.end.row &&
  278 + this.start.column === range.start.column &&
  279 + this.end.column === range.end.column;
  280 + }
  281 +
  282 + isMultiLine(): boolean {
  283 + return (this.start.row !== this.end.row);
  284 + }
  285 +
  286 + isStart(row: number, column: number): boolean {
  287 + return this.start.row === row && this.start.column === column;
  288 + }
  289 +
  290 + moveBy(row: number, column: number): void {
  291 + this.start.row += row;
  292 + this.start.column += column;
  293 + this.end.row += row;
  294 + this.end.column += column;
  295 + }
  296 +
  297 + setEnd(row: number, column: number): void {
  298 + if (typeof row === 'object') {
  299 + this.end.column = (row as Ace.Point).column;
  300 + this.end.row = (row as Ace.Point).row;
  301 + } else {
  302 + this.end.row = row;
  303 + this.end.column = column;
  304 + }
  305 + }
  306 +
  307 + setStart(row: number, column: number): void {
  308 + if (typeof row === 'object') {
  309 + this.start.column = (row as Ace.Point).column;
  310 + this.start.row = (row as Ace.Point).row;
  311 + } else {
  312 + this.start.row = row;
  313 + this.start.column = column;
  314 + }
  315 + }
  316 +
  317 + toScreenRange(session: Ace.EditSession): Ace.Range {
  318 + const screenPosStart = session.documentToScreenPosition(this.start);
  319 + const screenPosEnd = session.documentToScreenPosition(this.end);
  320 +
  321 + return new Range(
  322 + screenPosStart.row, screenPosStart.column,
  323 + screenPosEnd.row, screenPosEnd.column
  324 + );
  325 + }
  326 +
  327 +}
... ...
... ... @@ -14,7 +14,7 @@
14 14 /// limitations under the License.
15 15 ///
16 16
17   -import * as ace from 'ace-builds';
  17 +import { Ace } from 'ace-builds';
18 18
19 19 export type tbMetaType = 'object' | 'function' | 'service' | 'property' | 'argument';
20 20
... ... @@ -41,7 +41,7 @@ export interface TbEditorCompletion {
41 41 children?: TbEditorCompletions;
42 42 }
43 43
44   -interface TbEditorAceCompletion extends ace.Ace.Completion {
  44 +interface TbEditorAceCompletion extends Ace.Completion {
45 45 isTbEditorAceCompletion: true;
46 46 snippet: string;
47 47 description?: string;
... ... @@ -50,7 +50,7 @@ interface TbEditorAceCompletion extends ace.Ace.Completion {
50 50 return?: FunctionArgType;
51 51 }
52 52
53   -export class TbEditorCompleter implements ace.Ace.Completer {
  53 +export class TbEditorCompleter implements Ace.Completer {
54 54
55 55 identifierRegexps: RegExp[] = [
56 56 /[a-zA-Z_0-9\$\-\u00A2-\u2000\u2070-\uFFFF.]/
... ... @@ -59,8 +59,8 @@ export class TbEditorCompleter implements ace.Ace.Completer {
59 59 constructor(private editorCompletions: TbEditorCompletions) {
60 60 }
61 61
62   - getCompletions(editor: ace.Ace.Editor, session: ace.Ace.EditSession,
63   - position: ace.Ace.Point, prefix: string, callback: ace.Ace.CompleterCallback): void {
  62 + getCompletions(editor: Ace.Editor, session: Ace.EditSession,
  63 + position: Ace.Point, prefix: string, callback: Ace.CompleterCallback): void {
64 64 const result = this.prepareCompletions(prefix);
65 65 if (result) {
66 66 callback(null, result);
... ... @@ -91,7 +91,7 @@ export class TbEditorCompleter implements ace.Ace.Completer {
91 91 return parts;
92 92 }
93 93
94   - private prepareCompletions(prefix: string): ace.Ace.Completion[] {
  94 + private prepareCompletions(prefix: string): Ace.Completion[] {
95 95 const path = this.resolvePath(prefix);
96 96 if (path !== null) {
97 97 return this.toAceCompletionsList(this.editorCompletions, path);
... ... @@ -100,8 +100,8 @@ export class TbEditorCompleter implements ace.Ace.Completer {
100 100 }
101 101 }
102 102
103   - private toAceCompletionsList(completions: TbEditorCompletions, parentPath: string[]): ace.Ace.Completion[] {
104   - const result: ace.Ace.Completion[] = [];
  103 + private toAceCompletionsList(completions: TbEditorCompletions, parentPath: string[]): Ace.Completion[] {
  104 + const result: Ace.Completion[] = [];
105 105 let targetCompletions = completions;
106 106 let parentPrefix = '';
107 107 if (parentPath.length) {
... ... @@ -116,7 +116,7 @@ export class TbEditorCompleter implements ace.Ace.Completer {
116 116 return result;
117 117 }
118 118
119   - private toAceCompletion(name: string, completion: TbEditorCompletion, parentPrefix: string): ace.Ace.Completion {
  119 + private toAceCompletion(name: string, completion: TbEditorCompletion, parentPrefix: string): Ace.Completion {
120 120 const aceCompletion: TbEditorAceCompletion = {
121 121 isTbEditorAceCompletion: true,
122 122 snippet: parentPrefix + name,
... ... @@ -175,7 +175,7 @@ export class TbEditorCompleter implements ace.Ace.Completer {
175 175 if (completion.args || completion.return) {
176 176 let functionInfoBlock = '<div class="tb-function-info">';
177 177 if (completion.args) {
178   - functionInfoBlock += '<div class="tb-api-title">Parameters</div>'
  178 + functionInfoBlock += '<div class="tb-api-title">Parameters</div>';
179 179 let argsTable = '<table class="tb-api-table"><tbody>';
180 180 const strArgs: string[] = [];
181 181 for (const arg of completion.args) {
... ... @@ -185,7 +185,7 @@ export class TbEditorCompleter implements ace.Ace.Completer {
185 185 }
186 186 strArg += '</code></td><td>';
187 187 if (arg.type) {
188   - strArg += `<code>${arg.type}</code>`
  188 + strArg += `<code>${arg.type}</code>`;
189 189 }
190 190 strArg += '</td><td class="arg-description">';
191 191 if (arg.description) {
... ...
... ... @@ -75,7 +75,6 @@
75 75 import './zone-flags';
76 76 import 'zone.js/dist/zone'; // Included with Angular CLI.
77 77 import 'core-js/es/array';
78   -import moment from 'moment';
79 78
80 79 /***************************************************************************************************
81 80 * APPLICATION IMPORTS
... ... @@ -87,26 +86,4 @@ import moment from 'moment';
87 86 * WIDGETS IMPORTS
88 87 */
89 88
90   -import cssjs from '@core/css/css';
91   -import { TbFlot } from '@home/components/widget/lib/flot-widget';
92   -import { TbAnalogueCompass } from '@home/components/widget/lib/analogue-compass';
93   -import { TbAnalogueRadialGauge } from '@home/components/widget/lib/analogue-radial-gauge';
94   -import { TbAnalogueLinearGauge } from '@home/components/widget/lib/analogue-linear-gauge';
95   -import { TbCanvasDigitalGauge } from '@home/components/widget/lib/digital-gauge';
96   -import { TbMapWidgetV2 } from '@home/components/widget/lib/maps/map-widget2';
97   -import { TbTripAnimationWidget } from '@app/modules/home/components/widget/trip-animation/trip-animation.component';
98   -
99   -import * as tinycolor_ from 'tinycolor2';
100   -
101   -const tinycolor = tinycolor_;
102   -
103   -(window as any).tinycolor = tinycolor;
104   -(window as any).cssjs = cssjs;
105   -(window as any).moment = moment;
106   -(window as any).TbFlot = TbFlot;
107   -(window as any).TbAnalogueCompass = TbAnalogueCompass;
108   -(window as any).TbAnalogueRadialGauge = TbAnalogueRadialGauge;
109   -(window as any).TbAnalogueLinearGauge = TbAnalogueLinearGauge;
110   -(window as any).TbCanvasDigitalGauge = TbCanvasDigitalGauge;
111   -(window as any).TbMapWidgetV2 = TbMapWidgetV2;
112   -(window as any).TbTripAnimationWidget = TbTripAnimationWidget;
  89 +(window as any).GAUGES_NO_AUTO_INIT = true;
... ...
... ... @@ -14,7 +14,7 @@
14 14 /// limitations under the License.
15 15 ///
16 16
17   -import * as L from 'leaflet'
  17 +import * as L from 'leaflet';
18 18
19 19 declare module 'leaflet' {
20 20
... ...
... ... @@ -34,6 +34,9 @@
34 34 "@home/*": ["src/app/modules/home/*"],
35 35 "jszip": [
36 36 "node_modules/jszip/dist/jszip.min.js"
  37 + ],
  38 + "ace": [
  39 + "node_modules/ace-builds/src-noconflict/ace.js"
37 40 ]
38 41 },
39 42 "lib": [
... ...