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,22 +27,22 @@
27 "src/assets", 27 "src/assets",
28 { 28 {
29 "glob": "worker-html.js", 29 "glob": "worker-html.js",
30 - "input": "./node_modules/ace-builds/src-min/", 30 + "input": "./node_modules/ace-builds/src-noconflict/",
31 "output": "/" 31 "output": "/"
32 }, 32 },
33 { 33 {
34 "glob": "worker-css.js", 34 "glob": "worker-css.js",
35 - "input": "./node_modules/ace-builds/src-min/", 35 + "input": "./node_modules/ace-builds/src-noconflict/",
36 "output": "/" 36 "output": "/"
37 }, 37 },
38 { 38 {
39 "glob": "worker-json.js", 39 "glob": "worker-json.js",
40 - "input": "./node_modules/ace-builds/src-min/", 40 + "input": "./node_modules/ace-builds/src-noconflict/",
41 "output": "/" 41 "output": "/"
42 }, 42 },
43 { 43 {
44 "glob": "worker-javascript.js", 44 "glob": "worker-javascript.js",
45 - "input": "./node_modules/ace-builds/src-min/", 45 + "input": "./node_modules/ace-builds/src-noconflict/",
46 "output": "/" 46 "output": "/"
47 }, 47 },
48 { 48 {
@@ -102,24 +102,6 @@ @@ -102,24 +102,6 @@
102 "node_modules/js-beautify/js/lib/beautify.js", 102 "node_modules/js-beautify/js/lib/beautify.js",
103 "node_modules/js-beautify/js/lib/beautify-css.js", 103 "node_modules/js-beautify/js/lib/beautify-css.js",
104 "node_modules/js-beautify/js/lib/beautify-html.js", 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 "node_modules/systemjs/dist/system.js", 105 "node_modules/systemjs/dist/system.js",
124 "node_modules/jstree/dist/jstree.min.js" 106 "node_modules/jstree/dist/jstree.min.js"
125 ], 107 ],
@@ -143,7 +125,11 @@ @@ -143,7 +125,11 @@
143 "hoist-non-react-statics", 125 "hoist-non-react-statics",
144 "classnames", 126 "classnames",
145 "raf", 127 "raf",
146 - "moment-timezone" 128 + "moment-timezone",
  129 + "tinycolor2",
  130 + "json-schema-defaults",
  131 + "leaflet-providers",
  132 + "lodash"
147 ] 133 ]
148 }, 134 },
149 "configurations": { 135 "configurations": {
@@ -69,16 +69,19 @@ export function addCondition(schema: JsonSettingsSchema, condition: string, excl @@ -69,16 +69,19 @@ export function addCondition(schema: JsonSettingsSchema, condition: string, excl
69 return { 69 return {
70 key: element, 70 key: element,
71 condition 71 condition
72 - } 72 + };
73 } 73 }
74 if (typeof element === 'object') { 74 if (typeof element === 'object') {
75 if (element.condition) { 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 return element; 83 return element;
82 }); 84 });
83 return schema; 85 return schema;
84 } 86 }
  87 +
@@ -19,10 +19,10 @@ import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; @@ -19,10 +19,10 @@ import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
19 import { Store } from '@ngrx/store'; 19 import { Store } from '@ngrx/store';
20 import { AppState } from '@core/core.state'; 20 import { AppState } from '@core/core.state';
21 import { ActionStatus, AuditLog } from '@shared/models/audit-log.models'; 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 import { DialogComponent } from '@shared/components/dialog.component'; 23 import { DialogComponent } from '@shared/components/dialog.component';
25 import { Router } from '@angular/router'; 24 import { Router } from '@angular/router';
  25 +import { getAce } from '@shared/models/ace/ace.models';
26 26
27 export interface AuditLogDetailsDialogData { 27 export interface AuditLogDetailsDialogData {
28 auditLog: AuditLog; 28 auditLog: AuditLog;
@@ -37,11 +37,9 @@ export class AuditLogDetailsDialogComponent extends DialogComponent<AuditLogDeta @@ -37,11 +37,9 @@ export class AuditLogDetailsDialogComponent extends DialogComponent<AuditLogDeta
37 37
38 @ViewChild('actionDataEditor', {static: true}) 38 @ViewChild('actionDataEditor', {static: true})
39 actionDataEditorElmRef: ElementRef; 39 actionDataEditorElmRef: ElementRef;
40 - private actionDataEditor: ace.Ace.Editor;  
41 40
42 @ViewChild('failureDetailsEditor', {static: true}) 41 @ViewChild('failureDetailsEditor', {static: true})
43 failureDetailsEditorElmRef: ElementRef; 42 failureDetailsEditorElmRef: ElementRef;
44 - private failureDetailsEditor: ace.Ace.Editor;  
45 43
46 auditLog: AuditLog; 44 auditLog: AuditLog;
47 displayFailureDetails: boolean; 45 displayFailureDetails: boolean;
@@ -62,15 +60,15 @@ export class AuditLogDetailsDialogComponent extends DialogComponent<AuditLogDeta @@ -62,15 +60,15 @@ export class AuditLogDetailsDialogComponent extends DialogComponent<AuditLogDeta
62 this.actionData = this.auditLog.actionData ? JSON.stringify(this.auditLog.actionData, null, 2) : ''; 60 this.actionData = this.auditLog.actionData ? JSON.stringify(this.auditLog.actionData, null, 2) : '';
63 this.actionFailureDetails = this.auditLog.actionFailureDetails; 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 if (this.displayFailureDetails) { 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 const editorElement = editorElementRef.nativeElement; 70 const editorElement = editorElementRef.nativeElement;
73 - let editorOptions: Partial<ace.Ace.EditorOptions> = { 71 + let editorOptions: Partial<Ace.EditorOptions> = {
74 mode: 'ace/mode/java', 72 mode: 'ace/mode/java',
75 theme: 'ace/theme/github', 73 theme: 'ace/theme/github',
76 showGutter: false, 74 showGutter: false,
@@ -85,14 +83,17 @@ export class AuditLogDetailsDialogComponent extends DialogComponent<AuditLogDeta @@ -85,14 +83,17 @@ export class AuditLogDetailsDialogComponent extends DialogComponent<AuditLogDeta
85 }; 83 };
86 84
87 editorOptions = {...editorOptions, ...advancedOptions}; 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 let newHeight = 200; 97 let newHeight = 200;
97 let newWidth = 600; 98 let newWidth = 600;
98 if (content && content.length > 0) { 99 if (content && content.length > 0) {
@@ -19,10 +19,11 @@ import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; @@ -19,10 +19,11 @@ import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
19 import { Store } from '@ngrx/store'; 19 import { Store } from '@ngrx/store';
20 import { AppState } from '@core/core.state'; 20 import { AppState } from '@core/core.state';
21 21
22 -import * as ace from 'ace-builds'; 22 +import { Ace } from 'ace-builds';
23 import { DialogComponent } from '@shared/components/dialog.component'; 23 import { DialogComponent } from '@shared/components/dialog.component';
24 import { Router } from '@angular/router'; 24 import { Router } from '@angular/router';
25 import { ContentType, contentTypesMap } from '@shared/models/constants'; 25 import { ContentType, contentTypesMap } from '@shared/models/constants';
  26 +import { getAce } from '@shared/models/ace/ace.models';
26 27
27 export interface EventContentDialogData { 28 export interface EventContentDialogData {
28 content: string; 29 content: string;
@@ -39,7 +40,6 @@ export class EventContentDialogComponent extends DialogComponent<EventContentDia @@ -39,7 +40,6 @@ export class EventContentDialogComponent extends DialogComponent<EventContentDia
39 40
40 @ViewChild('eventContentEditor', {static: true}) 41 @ViewChild('eventContentEditor', {static: true})
41 eventContentEditorElmRef: ElementRef; 42 eventContentEditorElmRef: ElementRef;
42 - private eventContentEditor: ace.Ace.Editor;  
43 43
44 content: string; 44 content: string;
45 title: string; 45 title: string;
@@ -58,10 +58,10 @@ export class EventContentDialogComponent extends DialogComponent<EventContentDia @@ -58,10 +58,10 @@ export class EventContentDialogComponent extends DialogComponent<EventContentDia
58 this.title = this.data.title; 58 this.title = this.data.title;
59 this.contentType = this.data.contentType; 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 const editorElement = editorElementRef.nativeElement; 65 const editorElement = editorElementRef.nativeElement;
66 let mode = 'java'; 66 let mode = 'java';
67 if (this.contentType) { 67 if (this.contentType) {
@@ -70,7 +70,7 @@ export class EventContentDialogComponent extends DialogComponent<EventContentDia @@ -70,7 +70,7 @@ export class EventContentDialogComponent extends DialogComponent<EventContentDia
70 content = js_beautify(content, {indent_size: 4}); 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 mode: `ace/mode/${mode}`, 74 mode: `ace/mode/${mode}`,
75 theme: 'ace/theme/github', 75 theme: 'ace/theme/github',
76 showGutter: false, 76 showGutter: false,
@@ -85,14 +85,17 @@ export class EventContentDialogComponent extends DialogComponent<EventContentDia @@ -85,14 +85,17 @@ export class EventContentDialogComponent extends DialogComponent<EventContentDia
85 }; 85 };
86 86
87 editorOptions = {...editorOptions, ...advancedOptions}; 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 let newHeight = 400; 99 let newHeight = 400;
97 let newWidth = 600; 100 let newWidth = 600;
98 if (content && content.length > 0) { 101 if (content && content.length > 0) {
@@ -220,7 +220,7 @@ export class KeyFilterDialogComponent extends @@ -220,7 +220,7 @@ export class KeyFilterDialogComponent extends
220 let keyNameObservable: Observable<Array<string>>; 220 let keyNameObservable: Observable<Array<string>>;
221 switch (this.keyFilterFormGroup.get('key.type').value) { 221 switch (this.keyFilterFormGroup.get('key.type').value) {
222 case EntityKeyType.ENTITY_FIELD: 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 break; 224 break;
225 case EntityKeyType.ATTRIBUTE: 225 case EntityKeyType.ATTRIBUTE:
226 keyNameObservable = this.deviceProfileService.getDeviceProfileDevicesAttributesKeys( 226 keyNameObservable = this.deviceProfileService.getDeviceProfileDevicesAttributesKeys(
@@ -32,11 +32,15 @@ import { PageComponent } from '@shared/components/page.component'; @@ -32,11 +32,15 @@ import { PageComponent } from '@shared/components/page.component';
32 import { Store } from '@ngrx/store'; 32 import { Store } from '@ngrx/store';
33 import { AppState } from '@core/core.state'; 33 import { AppState } from '@core/core.state';
34 import { CustomActionDescriptor } from '@shared/models/widget.models'; 34 import { CustomActionDescriptor } from '@shared/models/widget.models';
35 -import * as ace from 'ace-builds'; 35 +import { Ace } from 'ace-builds';
36 import { CancelAnimationFrame, RafService } from '@core/services/raf.service'; 36 import { CancelAnimationFrame, RafService } from '@core/services/raf.service';
37 import { css_beautify, html_beautify } from 'js-beautify'; 37 import { css_beautify, html_beautify } from 'js-beautify';
38 import { ResizeObserver } from '@juggle/resize-observer'; 38 import { ResizeObserver } from '@juggle/resize-observer';
39 import { CustomPrettyActionEditorCompleter } from '@home/components/widget/action/custom-action.models'; 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 @Component({ 45 @Component({
42 selector: 'tb-custom-action-pretty-resources-tabs', 46 selector: 'tb-custom-action-pretty-resources-tabs',
@@ -64,11 +68,11 @@ export class CustomActionPrettyResourcesTabsComponent extends PageComponent impl @@ -64,11 +68,11 @@ export class CustomActionPrettyResourcesTabsComponent extends PageComponent impl
64 htmlFullscreen = false; 68 htmlFullscreen = false;
65 cssFullscreen = false; 69 cssFullscreen = false;
66 70
67 - aceEditors: ace.Ace.Editor[] = []; 71 + aceEditors: Ace.Editor[] = [];
68 editorsResizeCafs: {[editorId: string]: CancelAnimationFrame} = {}; 72 editorsResizeCafs: {[editorId: string]: CancelAnimationFrame} = {};
69 aceResize$: ResizeObserver; 73 aceResize$: ResizeObserver;
70 - htmlEditor: ace.Ace.Editor;  
71 - cssEditor: ace.Ace.Editor; 74 + htmlEditor: Ace.Editor;
  75 + cssEditor: Ace.Editor;
72 setValuesPending = false; 76 setValuesPending = false;
73 77
74 customPrettyActionEditorCompleter = CustomPrettyActionEditorCompleter; 78 customPrettyActionEditorCompleter = CustomPrettyActionEditorCompleter;
@@ -80,11 +84,12 @@ export class CustomActionPrettyResourcesTabsComponent extends PageComponent impl @@ -80,11 +84,12 @@ export class CustomActionPrettyResourcesTabsComponent extends PageComponent impl
80 } 84 }
81 85
82 ngOnInit(): void { 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 ngOnDestroy(): void { 95 ngOnDestroy(): void {
@@ -94,7 +99,7 @@ export class CustomActionPrettyResourcesTabsComponent extends PageComponent impl @@ -94,7 +99,7 @@ export class CustomActionPrettyResourcesTabsComponent extends PageComponent impl
94 ngOnChanges(changes: SimpleChanges): void { 99 ngOnChanges(changes: SimpleChanges): void {
95 for (const propName of Object.keys(changes)) { 100 for (const propName of Object.keys(changes)) {
96 if (propName === 'action') { 101 if (propName === 'action') {
97 - if (this.aceEditors.length) { 102 + if (this.aceEditors.length === 2) {
98 this.setAceEditorValues(); 103 this.setAceEditorValues();
99 } else { 104 } else {
100 this.setValuesPending = true; 105 this.setValuesPending = true;
@@ -152,34 +157,46 @@ export class CustomActionPrettyResourcesTabsComponent extends PageComponent impl @@ -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 this.aceResize$ = new ResizeObserver((entries) => { 161 this.aceResize$ = new ResizeObserver((entries) => {
157 entries.forEach((entry) => { 162 entries.forEach((entry) => {
158 const editor = this.aceEditors.find(aceEditor => aceEditor.container === entry.target); 163 const editor = this.aceEditors.find(aceEditor => aceEditor.container === entry.target);
159 this.onAceEditorResize(editor); 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 const editorElement = editorElementRef.nativeElement; 198 const editorElement = editorElementRef.nativeElement;
182 - let editorOptions: Partial<ace.Ace.EditorOptions> = { 199 + let editorOptions: Partial<Ace.EditorOptions> = {
183 mode: `ace/mode/${mode}`, 200 mode: `ace/mode/${mode}`,
184 showGutter: true, 201 showGutter: true,
185 showPrintMargin: true 202 showPrintMargin: true
@@ -190,11 +207,15 @@ export class CustomActionPrettyResourcesTabsComponent extends PageComponent impl @@ -190,11 +207,15 @@ export class CustomActionPrettyResourcesTabsComponent extends PageComponent impl
190 enableLiveAutocompletion: true 207 enableLiveAutocompletion: true
191 }; 208 };
192 editorOptions = {...editorOptions, ...advancedOptions}; 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 private setAceEditorValues() { 221 private setAceEditorValues() {
@@ -202,7 +223,7 @@ export class CustomActionPrettyResourcesTabsComponent extends PageComponent impl @@ -202,7 +223,7 @@ export class CustomActionPrettyResourcesTabsComponent extends PageComponent impl
202 this.cssEditor.setValue(this.action.customCss ? this.action.customCss : '', -1); 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 if (this.editorsResizeCafs[aceEditor.id]) { 227 if (this.editorsResizeCafs[aceEditor.id]) {
207 this.editorsResizeCafs[aceEditor.id](); 228 this.editorsResizeCafs[aceEditor.id]();
208 delete this.editorsResizeCafs[aceEditor.id]; 229 delete this.editorsResizeCafs[aceEditor.id];
@@ -103,7 +103,7 @@ export class GatewayFormComponent extends PageComponent implements OnInit, OnDes @@ -103,7 +103,7 @@ export class GatewayFormComponent extends PageComponent implements OnInit, OnDes
103 gatewayType: string; 103 gatewayType: string;
104 gatewayConfigurationGroup: FormGroup; 104 gatewayConfigurationGroup: FormGroup;
105 securityTypes = SecurityTypeTranslationMap; 105 securityTypes = SecurityTypeTranslationMap;
106 - gatewayLogLevels = Object.values(GatewayLogLevel); 106 + gatewayLogLevels = Object.keys(GatewayLogLevel).map(itm => GatewayLogLevel[itm]);
107 connectorTypes = Object.keys(ConnectorType); 107 connectorTypes = Object.keys(ConnectorType);
108 storageTypes = StorageTypeTranslationMap; 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,12 +43,14 @@ import { Observable, of } from 'rxjs';
43 import { Polyline } from './polyline'; 43 import { Polyline } from './polyline';
44 import { Polygon } from './polygon'; 44 import { Polygon } from './polygon';
45 import { 45 import {
46 - createLoadingDiv,  
47 createTooltip, 46 createTooltip,
  47 +} from '@home/components/widget/lib/maps/maps-utils';
  48 +import {
  49 + createLoadingDiv,
48 parseArray, 50 parseArray,
49 parseData, 51 parseData,
50 safeExecute 52 safeExecute
51 -} from '@home/components/widget/lib/maps/maps-utils'; 53 +} from '@home/components/widget/lib/maps/common-maps-utils';
52 import { WidgetContext } from '@home/models/widget-component.models'; 54 import { WidgetContext } from '@home/models/widget-component.models';
53 import { deepClone, isDefinedAndNotNull, isEmptyStr, isString } from '@core/utils'; 55 import { deepClone, isDefinedAndNotNull, isEmptyStr, isString } from '@core/utils';
54 56
@@ -141,7 +143,7 @@ export default abstract class LeafletMap { @@ -141,7 +143,7 @@ export default abstract class LeafletMap {
141 const header = document.createElement('p'); 143 const header = document.createElement('p');
142 header.appendChild(document.createTextNode('Select entity:')); 144 header.appendChild(document.createTextNode('Select entity:'));
143 header.setAttribute('style', 'font-size: 14px; margin: 8px 0'); 145 header.setAttribute('style', 'font-size: 14px; margin: 8px 0');
144 - datasourcesList.append(header); 146 + datasourcesList.appendChild(header);
145 this.datasources.forEach(ds => { 147 this.datasources.forEach(ds => {
146 const dsItem = document.createElement('p'); 148 const dsItem = document.createElement('p');
147 dsItem.appendChild(document.createTextNode(ds.entityName)); 149 dsItem.appendChild(document.createTextNode(ds.entityName));
@@ -158,9 +160,9 @@ export default abstract class LeafletMap { @@ -158,9 +160,9 @@ export default abstract class LeafletMap {
158 this.createMarker(ds.entityName, updatedEnttity, this.datasources, this.options); 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 const deleteBtn = document.createElement('a'); 166 const deleteBtn = document.createElement('a');
165 deleteBtn.appendChild(document.createTextNode('Discard changes')); 167 deleteBtn.appendChild(document.createTextNode('Discard changes'));
166 deleteBtn.onclick = () => { 168 deleteBtn.onclick = () => {
@@ -170,7 +172,7 @@ export default abstract class LeafletMap { @@ -170,7 +172,7 @@ export default abstract class LeafletMap {
170 this.addMarkers.splice(markerIndex, 1); 172 this.addMarkers.splice(markerIndex, 1);
171 } 173 }
172 }; 174 };
173 - datasourcesList.append(deleteBtn); 175 + datasourcesList.appendChild(deleteBtn);
174 const popup = L.popup(); 176 const popup = L.popup();
175 popup.setContent(datasourcesList); 177 popup.setContent(datasourcesList);
176 newMarker.bindPopup(popup).openPopup(); 178 newMarker.bindPopup(popup).openPopup();
@@ -222,7 +224,7 @@ export default abstract class LeafletMap { @@ -222,7 +224,7 @@ export default abstract class LeafletMap {
222 const header = document.createElement('p'); 224 const header = document.createElement('p');
223 header.appendChild(document.createTextNode('Select entity:')); 225 header.appendChild(document.createTextNode('Select entity:'));
224 header.setAttribute('style', 'font-size: 14px; margin: 8px 0'); 226 header.setAttribute('style', 'font-size: 14px; margin: 8px 0');
225 - datasourcesList.append(header); 227 + datasourcesList.appendChild(header);
226 this.datasources.forEach(ds => { 228 this.datasources.forEach(ds => {
227 const dsItem = document.createElement('p'); 229 const dsItem = document.createElement('p');
228 dsItem.appendChild(document.createTextNode(ds.entityName)); 230 dsItem.appendChild(document.createTextNode(ds.entityName));
@@ -238,9 +240,9 @@ export default abstract class LeafletMap { @@ -238,9 +240,9 @@ export default abstract class LeafletMap {
238 this.deletePolygon(ds.entityName); 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 const deleteBtn = document.createElement('a'); 246 const deleteBtn = document.createElement('a');
245 deleteBtn.appendChild(document.createTextNode('Discard changes')); 247 deleteBtn.appendChild(document.createTextNode('Discard changes'));
246 deleteBtn.onclick = () => { 248 deleteBtn.onclick = () => {
@@ -250,7 +252,7 @@ export default abstract class LeafletMap { @@ -250,7 +252,7 @@ export default abstract class LeafletMap {
250 this.addPolygons.splice(polygonIndex, 1); 252 this.addPolygons.splice(polygonIndex, 1);
251 } 253 }
252 }; 254 };
253 - datasourcesList.append(deleteBtn); 255 + datasourcesList.appendChild(deleteBtn);
254 const popup = L.popup(); 256 const popup = L.popup();
255 popup.setContent(datasourcesList); 257 popup.setContent(datasourcesList);
256 newPolygon.bindPopup(popup).openPopup(); 258 newPolygon.bindPopup(popup).openPopup();
@@ -289,7 +291,7 @@ export default abstract class LeafletMap { @@ -289,7 +291,7 @@ export default abstract class LeafletMap {
289 if (!this.loadingDiv) { 291 if (!this.loadingDiv) {
290 this.loadingDiv = createLoadingDiv(this.ctx.translate.instant('common.loading')); 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 } else { 295 } else {
294 if (this.loadingDiv) { 296 if (this.loadingDiv) {
295 this.loadingDiv.remove(); 297 this.loadingDiv.remove();
@@ -609,14 +611,13 @@ export default abstract class LeafletMap { @@ -609,14 +611,13 @@ export default abstract class LeafletMap {
609 } 611 }
610 612
611 updatePoints(pointsData: FormattedData[][], getTooltip: (point: FormattedData) => string) { 613 updatePoints(pointsData: FormattedData[][], getTooltip: (point: FormattedData) => string) {
612 - if(pointsData.length) { 614 + if (pointsData.length) {
613 if (this.points) { 615 if (this.points) {
614 this.map.removeLayer(this.points); 616 this.map.removeLayer(this.points);
615 } 617 }
616 this.points = new FeatureGroup(); 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 pointsList.filter(pdata => !!this.convertPosition(pdata)).forEach(data => { 621 pointsList.filter(pdata => !!this.convertPosition(pdata)).forEach(data => {
621 const point = L.circleMarker(this.convertPosition(data), { 622 const point = L.circleMarker(this.convertPosition(data), {
622 color: this.options.pointColor, 623 color: this.options.pointColor,
@@ -630,7 +631,7 @@ export default abstract class LeafletMap { @@ -630,7 +631,7 @@ export default abstract class LeafletMap {
630 this.points.addLayer(point); 631 this.points.addLayer(point);
631 }); 632 });
632 } 633 }
633 - if(pointsData.length) { 634 + if (pointsData.length) {
634 this.map.addLayer(this.points); 635 this.map.addLayer(this.points);
635 } 636 }
636 } 637 }
@@ -14,7 +14,6 @@ @@ -14,7 +14,6 @@
14 /// limitations under the License. 14 /// limitations under the License.
15 /// 15 ///
16 16
17 -import { LatLngTuple } from 'leaflet';  
18 import { Datasource } from '@app/shared/models/widget.models'; 17 import { Datasource } from '@app/shared/models/widget.models';
19 import { EntityType } from '@shared/models/entity-type.models'; 18 import { EntityType } from '@shared/models/entity-type.models';
20 import tinycolor from 'tinycolor2'; 19 import tinycolor from 'tinycolor2';
@@ -47,7 +46,7 @@ export type MapSettings = { @@ -47,7 +46,7 @@ export type MapSettings = {
47 provider?: MapProviders; 46 provider?: MapProviders;
48 credentials?: any; // declare credentials format 47 credentials?: any; // declare credentials format
49 gmApiKey?: string; 48 gmApiKey?: string;
50 - defaultCenterPosition?: LatLngTuple; 49 + defaultCenterPosition?: [number, number];
51 markerClusteringSetting?; 50 markerClusteringSetting?;
52 useDefaultCenterPosition?: boolean; 51 useDefaultCenterPosition?: boolean;
53 gmDefaultMapType?: string; 52 gmDefaultMapType?: string;
@@ -15,9 +15,10 @@ @@ -15,9 +15,10 @@
15 /// 15 ///
16 16
17 import { JsonSettingsSchema } from '@shared/models/widget.models'; 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 export interface MapWidgetInterface { 20 export interface MapWidgetInterface {
  21 + map?: any;
21 resize(); 22 resize();
22 update(); 23 update();
23 onInit(); 24 onInit();
@@ -31,20 +31,25 @@ import { @@ -31,20 +31,25 @@ import {
31 routeMapSettingsSchema 31 routeMapSettingsSchema
32 } from './schemes'; 32 } from './schemes';
33 import { MapWidgetInterface, MapWidgetStaticInterface } from './map-widget.interface'; 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 import { WidgetContext } from '@app/modules/home/models/widget-component.models'; 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 import { Datasource, DatasourceData, JsonSettingsSchema, WidgetActionDescriptor } from '@shared/models/widget.models'; 43 import { Datasource, DatasourceData, JsonSettingsSchema, WidgetActionDescriptor } from '@shared/models/widget.models';
38 import { EntityId } from '@shared/models/id/entity-id'; 44 import { EntityId } from '@shared/models/id/entity-id';
39 import { AttributeScope, DataKeyType, LatestTelemetry } from '@shared/models/telemetry/telemetry.models'; 45 import { AttributeScope, DataKeyType, LatestTelemetry } from '@shared/models/telemetry/telemetry.models';
40 import { AttributeService } from '@core/http/attribute.service'; 46 import { AttributeService } from '@core/http/attribute.service';
41 import { TranslateService } from '@ngx-translate/core'; 47 import { TranslateService } from '@ngx-translate/core';
42 import { UtilsService } from '@core/services/utils.service'; 48 import { UtilsService } from '@core/services/utils.service';
43 -import _ from 'lodash';  
44 import { EntityDataPageLink } from '@shared/models/query/query.models'; 49 import { EntityDataPageLink } from '@shared/models/query/query.models';
45 import { isDefined } from '@core/utils'; 50 import { isDefined } from '@core/utils';
46 import { forkJoin, Observable, of } from 'rxjs'; 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 // @dynamic 54 // @dynamic
50 export class MapWidgetController implements MapWidgetInterface { 55 export class MapWidgetController implements MapWidgetInterface {
@@ -70,7 +75,7 @@ export class MapWidgetController implements MapWidgetInterface { @@ -70,7 +75,7 @@ export class MapWidgetController implements MapWidgetInterface {
70 this.settings.markerClick = this.getDescriptors('markerClick'); 75 this.settings.markerClick = this.getDescriptors('markerClick');
71 this.settings.polygonClick = this.getDescriptors('polygonClick'); 76 this.settings.polygonClick = this.getDescriptors('polygonClick');
72 77
73 - const MapClass = providerSets[this.provider]?.MapClass; 78 + const MapClass = providerClass[this.provider];
74 if (!MapClass) { 79 if (!MapClass) {
75 return; 80 return;
76 } 81 }
@@ -101,19 +106,7 @@ export class MapWidgetController implements MapWidgetInterface { @@ -101,19 +106,7 @@ export class MapWidgetController implements MapWidgetInterface {
101 } 106 }
102 107
103 public static getProvidersSchema(mapProvider: MapProviders, ignoreImageMap = false) { 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 public static settingsSchema(mapProvider: MapProviders, drawRoutes: boolean): JsonSettingsSchema { 112 public static settingsSchema(mapProvider: MapProviders, drawRoutes: boolean): JsonSettingsSchema {
@@ -15,20 +15,8 @@ @@ -15,20 +15,8 @@
15 /// 15 ///
16 16
17 import L from 'leaflet'; 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 export function createTooltip(target: L.Layer, 21 export function createTooltip(target: L.Layer,
34 settings: MarkerSettings | PolylineSettings | PolygonSettings, 22 settings: MarkerSettings | PolylineSettings | PolygonSettings,
@@ -68,400 +56,3 @@ export function bindPopupActions(popup: L.Popup, settings: MarkerSettings | Poly @@ -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,14 +17,16 @@
17 import L, { Icon, LeafletMouseEvent } from 'leaflet'; 17 import L, { Icon, LeafletMouseEvent } from 'leaflet';
18 import { FormattedData, MarkerSettings } from './map-models'; 18 import { FormattedData, MarkerSettings } from './map-models';
19 import { 19 import {
20 - aspectCache,  
21 bindPopupActions, 20 bindPopupActions,
22 createTooltip, 21 createTooltip,
  22 +} from './maps-utils';
  23 +import {
  24 + aspectCache,
23 fillPattern, 25 fillPattern,
24 parseWithTranslation, 26 parseWithTranslation,
25 processPattern, 27 processPattern,
26 safeExecute 28 safeExecute
27 -} from './maps-utils'; 29 +} from './common-maps-utils';
28 import tinycolor from 'tinycolor2'; 30 import tinycolor from 'tinycolor2';
29 import { isDefined, isDefinedAndNotNull } from '@core/utils'; 31 import { isDefined, isDefinedAndNotNull } from '@core/utils';
30 import LeafletMap from './leaflet-map'; 32 import LeafletMap from './leaflet-map';
@@ -15,7 +15,8 @@ @@ -15,7 +15,8 @@
15 /// 15 ///
16 16
17 import L, { LatLngExpression, LeafletMouseEvent } from 'leaflet'; 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 import 'leaflet-editable/src/Leaflet.Editable'; 20 import 'leaflet-editable/src/Leaflet.Editable';
20 import { FormattedData, PolygonSettings } from './map-models'; 21 import { FormattedData, PolygonSettings } from './map-models';
21 22
@@ -18,7 +18,7 @@ import L, { PolylineDecoratorOptions } from 'leaflet'; @@ -18,7 +18,7 @@ import L, { PolylineDecoratorOptions } from 'leaflet';
18 import 'leaflet-polylinedecorator'; 18 import 'leaflet-polylinedecorator';
19 19
20 import { FormattedData, PolylineSettings } from './map-models'; 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 export class Polyline { 23 export class Polyline {
24 24
@@ -19,7 +19,7 @@ import LeafletMap from '../leaflet-map'; @@ -19,7 +19,7 @@ import LeafletMap from '../leaflet-map';
19 import { MapImage, PosFuncton, UnitedMapSettings } from '../map-models'; 19 import { MapImage, PosFuncton, UnitedMapSettings } from '../map-models';
20 import { Observable, ReplaySubject } from 'rxjs'; 20 import { Observable, ReplaySubject } from 'rxjs';
21 import { filter, map, mergeMap } from 'rxjs/operators'; 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 import { WidgetContext } from '@home/models/widget-component.models'; 23 import { WidgetContext } from '@home/models/widget-component.models';
24 import { DataSet, DatasourceType, widgetType } from '@shared/models/widget.models'; 24 import { DataSet, DatasourceType, widgetType } from '@shared/models/widget.models';
25 import { DataKeyType } from '@shared/models/telemetry/telemetry.models'; 25 import { DataKeyType } from '@shared/models/telemetry/telemetry.models';
@@ -14,11 +14,6 @@ @@ -14,11 +14,6 @@
14 /// limitations under the License. 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 import { OpenStreetMap } from './openstreet-map'; 17 import { OpenStreetMap } from './openstreet-map';
23 import { TencentMap } from './tencent-map'; 18 import { TencentMap } from './tencent-map';
24 import { GoogleMap } from './google-map'; 19 import { GoogleMap } from './google-map';
@@ -26,38 +21,11 @@ import { HEREMap } from './here-map'; @@ -26,38 +21,11 @@ import { HEREMap } from './here-map';
26 import { ImageMap } from './image-map'; 21 import { ImageMap } from './image-map';
27 import { Type } from '@angular/core'; 22 import { Type } from '@angular/core';
28 import LeafletMap from '@home/components/widget/lib/maps/leaflet-map'; 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,6 +14,8 @@
14 /// limitations under the License. 14 /// limitations under the License.
15 /// 15 ///
16 16
  17 +import { JsonSettingsSchema } from '@shared/models/widget.models';
  18 +
17 export const googleMapSettingsSchema = 19 export const googleMapSettingsSchema =
18 { 20 {
19 schema: { 21 schema: {
@@ -1096,3 +1098,31 @@ export const tripAnimationSchema = { @@ -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,7 +21,7 @@ import { UtilsService } from '@core/services/utils.service';
21 import { Store } from '@ngrx/store'; 21 import { Store } from '@ngrx/store';
22 import { AppState } from '@core/core.state'; 22 import { AppState } from '@core/core.state';
23 import { isDefined, isNumber } from '@core/utils'; 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 import * as tinycolor_ from 'tinycolor2'; 25 import * as tinycolor_ from 'tinycolor2';
26 import { ResizeObserver } from '@juggle/resize-observer'; 26 import { ResizeObserver } from '@juggle/resize-observer';
27 import GenericOptions = CanvasGauges.GenericOptions; 27 import GenericOptions = CanvasGauges.GenericOptions;
@@ -102,7 +102,7 @@ export class KnobComponent extends PageComponent implements OnInit, OnDestroy { @@ -102,7 +102,7 @@ export class KnobComponent extends PageComponent implements OnInit, OnDestroy {
102 private textMeasure: JQuery<HTMLElement>; 102 private textMeasure: JQuery<HTMLElement>;
103 private canvasBarElement: HTMLElement; 103 private canvasBarElement: HTMLElement;
104 104
105 - private canvasBar: CanvasDigitalGauge; 105 + private canvasBar: any; // CanvasDigitalGauge;
106 106
107 private knobResize$: ResizeObserver; 107 private knobResize$: ResizeObserver;
108 108
@@ -165,7 +165,6 @@ export class KnobComponent extends PageComponent implements OnInit, OnDestroy { @@ -165,7 +165,6 @@ export class KnobComponent extends PageComponent implements OnInit, OnDestroy {
165 animation: false 165 animation: false
166 }; 166 };
167 167
168 - this.canvasBar = new CanvasDigitalGauge(canvasBarData).draw();  
169 168
170 this.knob.on('click', (e) => { 169 this.knob.on('click', (e) => {
171 if (this.moving) { 170 if (this.moving) {
@@ -277,9 +276,6 @@ export class KnobComponent extends PageComponent implements OnInit, OnDestroy { @@ -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 const subscription = this.ctx.defaultSubscription; 279 const subscription = this.ctx.defaultSubscription;
284 const rpcEnabled = subscription.rpcEnabled; 280 const rpcEnabled = subscription.rpcEnabled;
285 281
@@ -297,13 +293,22 @@ export class KnobComponent extends PageComponent implements OnInit, OnDestroy { @@ -297,13 +293,22 @@ export class KnobComponent extends PageComponent implements OnInit, OnDestroy {
297 if (settings.setValueMethod && settings.setValueMethod.length) { 293 if (settings.setValueMethod && settings.setValueMethod.length) {
298 this.setValueMethod = settings.setValueMethod; 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 private degreeToRatio(degree: number): number { 314 private degreeToRatio(degree: number): number {
@@ -328,7 +333,9 @@ export class KnobComponent extends PageComponent implements OnInit, OnDestroy { @@ -328,7 +333,9 @@ export class KnobComponent extends PageComponent implements OnInit, OnDestroy {
328 const height = this.knobContainer.height(); 333 const height = this.knobContainer.height();
329 const size = Math.min(width, height); 334 const size = Math.min(width, height);
330 this.knob.css({width: size, height: size}); 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 this.setFontSize(this.knobTitle, this.title, this.knobTitleContainer.height(), this.knobTitleContainer.width()); 339 this.setFontSize(this.knobTitle, this.title, this.knobTitleContainer.height(), this.knobTitleContainer.width());
333 this.setFontSize(this.knobError, this.error, this.knobErrorContainer.height(), this.knobErrorContainer.width()); 340 this.setFontSize(this.knobError, this.error, this.knobErrorContainer.height(), this.knobErrorContainer.width());
334 const minmaxHeight = this.knobMinmaxContainer.height(); 341 const minmaxHeight = this.knobMinmaxContainer.height();
@@ -546,7 +546,7 @@ class TimeseriesDatasource implements DataSource<TimeseriesRow> { @@ -546,7 +546,7 @@ class TimeseriesDatasource implements DataSource<TimeseriesRow> {
546 } 546 }
547 } 547 }
548 } else { 548 } else {
549 - rows = Object.values(rowsMap); 549 + rows = Object.keys(rowsMap).map(itm => rowsMap[itm]);
550 } 550 }
551 return rows; 551 return rows;
552 } 552 }
@@ -27,28 +27,28 @@ import { @@ -27,28 +27,28 @@ import {
27 SecurityContext, 27 SecurityContext,
28 ViewChild 28 ViewChild
29 } from '@angular/core'; 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 import { addCondition, addGroupInfo, addToSchema, initSchema } from '@app/core/schema-utils'; 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 import { DomSanitizer } from '@angular/platform-browser'; 33 import { DomSanitizer } from '@angular/platform-browser';
35 import { WidgetContext } from '@app/modules/home/models/widget-component.models'; 34 import { WidgetContext } from '@app/modules/home/models/widget-component.models';
36 import { 35 import {
37 - findAngle, 36 + findAngle, getProviderSchema,
38 getRatio, 37 getRatio,
39 interpolateOnLineSegment, 38 interpolateOnLineSegment,
40 parseArray, 39 parseArray,
41 parseFunction, 40 parseFunction,
42 parseWithTranslation, 41 parseWithTranslation,
43 safeExecute 42 safeExecute
44 -} from '../lib/maps/maps-utils'; 43 +} from '@home/components/widget/lib/maps/common-maps-utils';
45 import { JsonSettingsSchema, WidgetConfig } from '@shared/models/widget.models'; 44 import { JsonSettingsSchema, WidgetConfig } from '@shared/models/widget.models';
46 import moment from 'moment'; 45 import moment from 'moment';
47 import { isUndefined } from '@core/utils'; 46 import { isUndefined } from '@core/utils';
48 import { ResizeObserver } from '@juggle/resize-observer'; 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 @Component({ 54 @Component({
@@ -67,7 +67,7 @@ export class TripAnimationComponent implements OnInit, AfterViewInit, OnDestroy @@ -67,7 +67,7 @@ export class TripAnimationComponent implements OnInit, AfterViewInit, OnDestroy
67 67
68 @ViewChild('map') mapContainer; 68 @ViewChild('map') mapContainer;
69 69
70 - mapWidget: MapWidgetController; 70 + mapWidget: MapWidgetInterface;
71 historicalData: FormattedData[][]; 71 historicalData: FormattedData[][];
72 normalizationStep: number; 72 normalizationStep: number;
73 interpolatedTimeData = []; 73 interpolatedTimeData = [];
@@ -85,7 +85,7 @@ export class TripAnimationComponent implements OnInit, AfterViewInit, OnDestroy @@ -85,7 +85,7 @@ export class TripAnimationComponent implements OnInit, AfterViewInit, OnDestroy
85 85
86 static getSettingsSchema(): JsonSettingsSchema { 86 static getSettingsSchema(): JsonSettingsSchema {
87 const schema = initSchema(); 87 const schema = initSchema();
88 - addToSchema(schema, TbMapWidgetV2.getProvidersSchema(null, true)); 88 + addToSchema(schema, getProviderSchema(null, true));
89 addGroupInfo(schema, 'Map Provider Settings'); 89 addGroupInfo(schema, 'Map Provider Settings');
90 addToSchema(schema, tripAnimationSchema); 90 addToSchema(schema, tripAnimationSchema);
91 addGroupInfo(schema, 'Trip Animation Settings'); 91 addGroupInfo(schema, 'Trip Animation Settings');
@@ -106,7 +106,7 @@ export class TripAnimationComponent implements OnInit, AfterViewInit, OnDestroy @@ -106,7 +106,7 @@ export class TripAnimationComponent implements OnInit, AfterViewInit, OnDestroy
106 buttonColor: tinycolor(this.widgetConfig.color).setAlpha(0.54).toRgbString(), 106 buttonColor: tinycolor(this.widgetConfig.color).setAlpha(0.54).toRgbString(),
107 disabledButtonColor: tinycolor(this.widgetConfig.color).setAlpha(0.3).toRgbString(), 107 disabledButtonColor: tinycolor(this.widgetConfig.color).setAlpha(0.3).toRgbString(),
108 rotationAngle: 0 108 rotationAngle: 0
109 - } 109 + };
110 this.settings = { ...settings, ...this.ctx.settings }; 110 this.settings = { ...settings, ...this.ctx.settings };
111 this.useAnchors = this.settings.showPoints && this.settings.usePointAsAnchor; 111 this.useAnchors = this.settings.showPoints && this.settings.usePointAsAnchor;
112 this.settings.pointAsAnchorFunction = parseFunction(this.settings.pointAsAnchorFunction, ['data', 'dsData', 'dsIndex']); 112 this.settings.pointAsAnchorFunction = parseFunction(this.settings.pointAsAnchorFunction, ['data', 'dsData', 'dsIndex']);
@@ -122,15 +122,19 @@ export class TripAnimationComponent implements OnInit, AfterViewInit, OnDestroy @@ -122,15 +122,19 @@ export class TripAnimationComponent implements OnInit, AfterViewInit, OnDestroy
122 } 122 }
123 this.mapWidget.map.map?.invalidateSize(); 123 this.mapWidget.map.map?.invalidateSize();
124 this.cd.detectChanges(); 124 this.cd.detectChanges();
125 - } 125 + };
126 } 126 }
127 127
128 ngAfterViewInit() { 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 ngOnDestroy() { 140 ngOnDestroy() {
@@ -142,8 +146,8 @@ export class TripAnimationComponent implements OnInit, AfterViewInit, OnDestroy @@ -142,8 +146,8 @@ export class TripAnimationComponent implements OnInit, AfterViewInit, OnDestroy
142 timeUpdated(time: number) { 146 timeUpdated(time: number) {
143 this.currentTime = time; 147 this.currentTime = time;
144 const currentPosition = this.interpolatedTimeData 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 if (isUndefined(currentPosition[j])) { 151 if (isUndefined(currentPosition[j])) {
148 const timePoints = Object.keys(this.interpolatedTimeData[j]).map(item => parseInt(item, 10)); 152 const timePoints = Object.keys(this.interpolatedTimeData[j]).map(item => parseInt(item, 10));
149 for (let i = 1; i < timePoints.length; i++) { 153 for (let i = 1; i < timePoints.length; i++) {
@@ -155,13 +159,13 @@ export class TripAnimationComponent implements OnInit, AfterViewInit, OnDestroy @@ -155,13 +159,13 @@ export class TripAnimationComponent implements OnInit, AfterViewInit, OnDestroy
155 ...beforePosition, 159 ...beforePosition,
156 time, 160 time,
157 ...interpolateOnLineSegment(beforePosition, afterPosition, this.settings.latKeyName, this.settings.lngKeyName, ratio) 161 ...interpolateOnLineSegment(beforePosition, afterPosition, this.settings.latKeyName, this.settings.lngKeyName, ratio)
158 - } 162 + };
159 break; 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 if (isUndefined(currentPosition[j])) { 169 if (isUndefined(currentPosition[j])) {
166 currentPosition[j] = this.calculateLastPoints(this.interpolatedTimeData[j], time); 170 currentPosition[j] = this.calculateLastPoints(this.interpolatedTimeData[j], time);
167 } 171 }
@@ -179,7 +183,7 @@ export class TripAnimationComponent implements OnInit, AfterViewInit, OnDestroy @@ -179,7 +183,7 @@ export class TripAnimationComponent implements OnInit, AfterViewInit, OnDestroy
179 } 183 }
180 this.mapWidget.map.updateMarkers(currentPosition, true, (trip) => { 184 this.mapWidget.map.updateMarkers(currentPosition, true, (trip) => {
181 this.activeTrip = trip; 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,14 +191,14 @@ export class TripAnimationComponent implements OnInit, AfterViewInit, OnDestroy
187 setActiveTrip() { 191 setActiveTrip() {
188 } 192 }
189 193
190 - private calculateLastPoints(dataSource: dataMap, time: number): FormattedData { 194 + private calculateLastPoints(dataSource: DataMap, time: number): FormattedData {
191 const timeArr = Object.keys(dataSource); 195 const timeArr = Object.keys(dataSource);
192 - let index = timeArr.findIndex((dtime, index) => { 196 + let index = timeArr.findIndex((dtime) => {
193 return Number(dtime) >= time; 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 index--; 202 index--;
199 } 203 }
200 } else { 204 } else {
@@ -210,7 +214,7 @@ export class TripAnimationComponent implements OnInit, AfterViewInit, OnDestroy @@ -210,7 +214,7 @@ export class TripAnimationComponent implements OnInit, AfterViewInit, OnDestroy
210 this.maxTime = dataSource[dataSource.length - 1]?.time || -Infinity; 214 this.maxTime = dataSource[dataSource.length - 1]?.time || -Infinity;
211 this.interpolatedTimeData[index] = this.interpolateArray(dataSource); 215 this.interpolatedTimeData[index] = this.interpolateArray(dataSource);
212 }); 216 });
213 - if(!this.activeTrip){ 217 + if (!this.activeTrip) {
214 this.activeTrip = this.interpolatedTimeData.map(dataSource => dataSource[this.minTime]).filter(ds => ds)[0]; 218 this.activeTrip = this.interpolatedTimeData.map(dataSource => dataSource[this.minTime]).filter(ds => ds)[0];
215 } 219 }
216 if (this.useAnchors) { 220 if (this.useAnchors) {
@@ -230,7 +234,7 @@ export class TripAnimationComponent implements OnInit, AfterViewInit, OnDestroy @@ -230,7 +234,7 @@ export class TripAnimationComponent implements OnInit, AfterViewInit, OnDestroy
230 234
231 private calcMainTooltip(points: FormattedData[]): void { 235 private calcMainTooltip(points: FormattedData[]): void {
232 const tooltips = []; 236 const tooltips = [];
233 - for (let point of points) { 237 + for (const point of points) {
234 tooltips.push(this.sanitizer.sanitize(SecurityContext.HTML, this.calcTooltip(point))); 238 tooltips.push(this.sanitizer.sanitize(SecurityContext.HTML, this.calcTooltip(point)));
235 } 239 }
236 this.mainTooltips = tooltips; 240 this.mainTooltips = tooltips;
@@ -259,7 +263,7 @@ export class TripAnimationComponent implements OnInit, AfterViewInit, OnDestroy @@ -259,7 +263,7 @@ export class TripAnimationComponent implements OnInit, AfterViewInit, OnDestroy
259 } 263 }
260 const timeStamp = Object.keys(result); 264 const timeStamp = Object.keys(result);
261 for (let i = 0; i < timeStamp.length - 1; i++) { 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 return result; 268 return result;
265 } 269 }
@@ -17,7 +17,7 @@ @@ -17,7 +17,7 @@
17 import { Inject, Injectable, Optional, Type } from '@angular/core'; 17 import { Inject, Injectable, Optional, Type } from '@angular/core';
18 import { DynamicComponentFactoryService } from '@core/services/dynamic-component-factory.service'; 18 import { DynamicComponentFactoryService } from '@core/services/dynamic-component-factory.service';
19 import { WidgetService } from '@core/http/widget.service'; 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 import { 21 import {
22 ErrorWidgetType, 22 ErrorWidgetType,
23 MissingWidgetType, 23 MissingWidgetType,
@@ -30,7 +30,7 @@ import cssjs from '@core/css/css'; @@ -30,7 +30,7 @@ import cssjs from '@core/css/css';
30 import { UtilsService } from '@core/services/utils.service'; 30 import { UtilsService } from '@core/services/utils.service';
31 import { ResourcesService } from '@core/services/resources.service'; 31 import { ResourcesService } from '@core/services/resources.service';
32 import { Widget, widgetActionSources, WidgetControllerDescriptor, WidgetType } from '@shared/models/widget.models'; 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 import { isFunction, isUndefined } from '@core/utils'; 34 import { isFunction, isUndefined } from '@core/utils';
35 import { TranslateService } from '@ngx-translate/core'; 35 import { TranslateService } from '@ngx-translate/core';
36 import { DynamicWidgetComponent } from '@home/components/widget/dynamic-widget.component'; 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,6 +42,10 @@ import { WidgetTypeId } from '@app/shared/models/id/widget-type-id';
42 import { TenantId } from '@app/shared/models/id/tenant-id'; 42 import { TenantId } from '@app/shared/models/id/tenant-id';
43 import { SharedModule } from '@shared/shared.module'; 43 import { SharedModule } from '@shared/shared.module';
44 import { MODULES_MAP } from '@shared/public-api'; 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 // @dynamic 50 // @dynamic
47 @Injectable() 51 @Injectable()
@@ -106,20 +110,77 @@ export class WidgetComponentService { @@ -106,20 +110,77 @@ export class WidgetComponentService {
106 } 110 }
107 const initSubject = new ReplaySubject(); 111 const initSubject = new ReplaySubject();
108 this.init$ = initSubject.asObservable(); 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 (e) => { 178 (e) => {
118 - let errorMessages = ['Failed to load default widget types!']; 179 + let errorMessages = ['Failed to load widget modules!'];
119 if (e && e.length) { 180 if (e && e.length) {
120 errorMessages = errorMessages.concat(e); 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 initSubject.error({ 184 initSubject.error({
124 widgetInfo: this.errorWidgetType, 185 widgetInfo: this.errorWidgetType,
125 errorMessages 186 errorMessages
@@ -32,7 +32,8 @@ import { NULL_UUID } from '@shared/models/id/has-uuid'; @@ -32,7 +32,8 @@ import { NULL_UUID } from '@shared/models/id/has-uuid';
32 import { Hotkey } from 'angular2-hotkeys'; 32 import { Hotkey } from 'angular2-hotkeys';
33 import { TranslateService } from '@ngx-translate/core'; 33 import { TranslateService } from '@ngx-translate/core';
34 import { getCurrentIsLoading } from '@app/core/interceptors/load.selectors'; 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 import { css_beautify, html_beautify } from 'js-beautify'; 37 import { css_beautify, html_beautify } from 'js-beautify';
37 import { CancelAnimationFrame, RafService } from '@core/services/raf.service'; 38 import { CancelAnimationFrame, RafService } from '@core/services/raf.service';
38 import { WINDOW } from '@core/services/window.service'; 39 import { WINDOW } from '@core/services/window.service';
@@ -44,10 +45,12 @@ import { @@ -44,10 +45,12 @@ import {
44 SaveWidgetTypeAsDialogComponent, 45 SaveWidgetTypeAsDialogComponent,
45 SaveWidgetTypeAsDialogResult 46 SaveWidgetTypeAsDialogResult
46 } from '@home/pages/widget/save-widget-type-as-dialog.component'; 47 } from '@home/pages/widget/save-widget-type-as-dialog.component';
47 -import { Subscription } from 'rxjs'; 48 +import { forkJoin, from, Subscription } from 'rxjs';
48 import { ResizeObserver } from '@juggle/resize-observer'; 49 import { ResizeObserver } from '@juggle/resize-observer';
49 import Timeout = NodeJS.Timeout; 50 import Timeout = NodeJS.Timeout;
50 import { widgetEditorCompleter } from '@home/pages/widget/widget-editor.models'; 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 // @dynamic 55 // @dynamic
53 @Component({ 56 @Component({
@@ -119,13 +122,13 @@ export class WidgetEditorComponent extends PageComponent implements OnInit, OnDe @@ -119,13 +122,13 @@ export class WidgetEditorComponent extends PageComponent implements OnInit, OnDe
119 javascriptFullscreen = false; 122 javascriptFullscreen = false;
120 iFrameFullscreen = false; 123 iFrameFullscreen = false;
121 124
122 - aceEditors: ace.Ace.Editor[] = []; 125 + aceEditors: Ace.Editor[] = [];
123 editorsResizeCafs: {[editorId: string]: CancelAnimationFrame} = {}; 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 aceResize$: ResizeObserver; 132 aceResize$: ResizeObserver;
130 133
131 onWindowMessageListener = this.onWindowMessage.bind(this); 134 onWindowMessageListener = this.onWindowMessage.bind(this);
@@ -277,51 +280,85 @@ export class WidgetEditorComponent extends PageComponent implements OnInit, OnDe @@ -277,51 +280,85 @@ export class WidgetEditorComponent extends PageComponent implements OnInit, OnDe
277 this.onAceEditorResize(editor); 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 private setAceEditorValues() { 364 private setAceEditorValues() {
@@ -332,9 +369,9 @@ export class WidgetEditorComponent extends PageComponent implements OnInit, OnDe @@ -332,9 +369,9 @@ export class WidgetEditorComponent extends PageComponent implements OnInit, OnDe
332 this.jsEditor.setValue(this.widget.controllerScript ? this.widget.controllerScript : '', -1); 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 const editorElement = editorElementRef.nativeElement; 373 const editorElement = editorElementRef.nativeElement;
337 - let editorOptions: Partial<ace.Ace.EditorOptions> = { 374 + let editorOptions: Partial<Ace.EditorOptions> = {
338 mode: `ace/mode/${mode}`, 375 mode: `ace/mode/${mode}`,
339 showGutter: true, 376 showGutter: true,
340 showPrintMargin: true 377 showPrintMargin: true
@@ -345,14 +382,18 @@ export class WidgetEditorComponent extends PageComponent implements OnInit, OnDe @@ -345,14 +382,18 @@ export class WidgetEditorComponent extends PageComponent implements OnInit, OnDe
345 enableLiveAutocompletion: true 382 enableLiveAutocompletion: true
346 }; 383 };
347 editorOptions = {...editorOptions, ...advancedOptions}; 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 if (this.editorsResizeCafs[aceEditor.id]) { 397 if (this.editorsResizeCafs[aceEditor.id]) {
357 this.editorsResizeCafs[aceEditor.id](); 398 this.editorsResizeCafs[aceEditor.id]();
358 delete this.editorsResizeCafs[aceEditor.id]; 399 delete this.editorsResizeCafs[aceEditor.id];
@@ -443,11 +484,11 @@ export class WidgetEditorComponent extends PageComponent implements OnInit, OnDe @@ -443,11 +484,11 @@ export class WidgetEditorComponent extends PageComponent implements OnInit, OnDe
443 if (details.columnNumber) { 484 if (details.columnNumber) {
444 column = details.columnNumber; 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 'ace_active-line', 'screenLine'); 488 'ace_active-line', 'screenLine');
448 this.errorMarkers.push(errorMarkerId); 489 this.errorMarkers.push(errorMarkerId);
449 const annotations = this.jsEditor.session.getAnnotations(); 490 const annotations = this.jsEditor.session.getAnnotations();
450 - const errorAnnotation: ace.Ace.Annotation = { 491 + const errorAnnotation: Ace.Annotation = {
451 row: line, 492 row: line,
452 column, 493 column,
453 text: details.message, 494 text: details.message,
@@ -25,7 +25,8 @@ import { @@ -25,7 +25,8 @@ import {
25 ViewEncapsulation 25 ViewEncapsulation
26 } from '@angular/core'; 26 } from '@angular/core';
27 import { ControlValueAccessor, FormControl, NG_VALIDATORS, NG_VALUE_ACCESSOR, Validator } from '@angular/forms'; 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 import { coerceBooleanProperty } from '@angular/cdk/coercion'; 30 import { coerceBooleanProperty } from '@angular/cdk/coercion';
30 import { ActionNotificationHide, ActionNotificationShow } from '@core/notification/notification.actions'; 31 import { ActionNotificationHide, ActionNotificationShow } from '@core/notification/notification.actions';
31 import { Store } from '@ngrx/store'; 32 import { Store } from '@ngrx/store';
@@ -60,7 +61,7 @@ export class JsFuncComponent implements OnInit, OnDestroy, ControlValueAccessor, @@ -60,7 +61,7 @@ export class JsFuncComponent implements OnInit, OnDestroy, ControlValueAccessor,
60 @ViewChild('javascriptEditor', {static: true}) 61 @ViewChild('javascriptEditor', {static: true})
61 javascriptEditorElmRef: ElementRef; 62 javascriptEditorElmRef: ElementRef;
62 63
63 - private jsEditor: ace.Ace.Editor; 64 + private jsEditor: Ace.Editor;
64 private editorsResizeCaf: CancelAnimationFrame; 65 private editorsResizeCaf: CancelAnimationFrame;
65 private editorResize$: ResizeObserver; 66 private editorResize$: ResizeObserver;
66 private ignoreChange = false; 67 private ignoreChange = false;
@@ -136,7 +137,7 @@ export class JsFuncComponent implements OnInit, OnDestroy, ControlValueAccessor, @@ -136,7 +137,7 @@ export class JsFuncComponent implements OnInit, OnDestroy, ControlValueAccessor,
136 }); 137 });
137 } 138 }
138 const editorElement = this.javascriptEditorElmRef.nativeElement; 139 const editorElement = this.javascriptEditorElmRef.nativeElement;
139 - let editorOptions: Partial<ace.Ace.EditorOptions> = { 140 + let editorOptions: Partial<Ace.EditorOptions> = {
140 mode: 'ace/mode/javascript', 141 mode: 'ace/mode/javascript',
141 showGutter: true, 142 showGutter: true,
142 showPrintMargin: true, 143 showPrintMargin: true,
@@ -150,22 +151,27 @@ export class JsFuncComponent implements OnInit, OnDestroy, ControlValueAccessor, @@ -150,22 +151,27 @@ export class JsFuncComponent implements OnInit, OnDestroy, ControlValueAccessor,
150 }; 151 };
151 152
152 editorOptions = {...editorOptions, ...advancedOptions}; 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 ngOnDestroy(): void { 177 ngOnDestroy(): void {
@@ -294,11 +300,11 @@ export class JsFuncComponent implements OnInit, OnDestroy, ControlValueAccessor, @@ -294,11 +300,11 @@ export class JsFuncComponent implements OnInit, OnDestroy, ControlValueAccessor,
294 if (details.columnNumber) { 300 if (details.columnNumber) {
295 column = details.columnNumber; 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 'ace_active-line', 'screenLine'); 304 'ace_active-line', 'screenLine');
299 this.errorMarkers.push(errorMarkerId); 305 this.errorMarkers.push(errorMarkerId);
300 const annotations = this.jsEditor.session.getAnnotations(); 306 const annotations = this.jsEditor.session.getAnnotations();
301 - const errorAnnotation: ace.Ace.Annotation = { 307 + const errorAnnotation: Ace.Annotation = {
302 row: line, 308 row: line,
303 column, 309 column,
304 text: details.message, 310 text: details.message,
@@ -26,7 +26,7 @@ import { @@ -26,7 +26,7 @@ import {
26 ViewChild 26 ViewChild
27 } from '@angular/core'; 27 } from '@angular/core';
28 import { ControlValueAccessor, FormControl, NG_VALIDATORS, NG_VALUE_ACCESSOR, Validator } from '@angular/forms'; 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 import { coerceBooleanProperty } from '@angular/cdk/coercion'; 30 import { coerceBooleanProperty } from '@angular/cdk/coercion';
31 import { ActionNotificationHide, ActionNotificationShow } from '@core/notification/notification.actions'; 31 import { ActionNotificationHide, ActionNotificationShow } from '@core/notification/notification.actions';
32 import { Store } from '@ngrx/store'; 32 import { Store } from '@ngrx/store';
@@ -35,6 +35,7 @@ import { ContentType, contentTypesMap } from '@shared/models/constants'; @@ -35,6 +35,7 @@ import { ContentType, contentTypesMap } from '@shared/models/constants';
35 import { CancelAnimationFrame, RafService } from '@core/services/raf.service'; 35 import { CancelAnimationFrame, RafService } from '@core/services/raf.service';
36 import { guid } from '@core/utils'; 36 import { guid } from '@core/utils';
37 import { ResizeObserver } from '@juggle/resize-observer'; 37 import { ResizeObserver } from '@juggle/resize-observer';
  38 +import { getAce } from '@shared/models/ace/ace.models';
38 39
39 @Component({ 40 @Component({
40 selector: 'tb-json-content', 41 selector: 'tb-json-content',
@@ -58,7 +59,7 @@ export class JsonContentComponent implements OnInit, ControlValueAccessor, Valid @@ -58,7 +59,7 @@ export class JsonContentComponent implements OnInit, ControlValueAccessor, Valid
58 @ViewChild('jsonEditor', {static: true}) 59 @ViewChild('jsonEditor', {static: true})
59 jsonEditorElmRef: ElementRef; 60 jsonEditorElmRef: ElementRef;
60 61
61 - private jsonEditor: ace.Ace.Editor; 62 + private jsonEditor: Ace.Editor;
62 private editorsResizeCaf: CancelAnimationFrame; 63 private editorsResizeCaf: CancelAnimationFrame;
63 private editorResize$: ResizeObserver; 64 private editorResize$: ResizeObserver;
64 private ignoreChange = false; 65 private ignoreChange = false;
@@ -123,7 +124,7 @@ export class JsonContentComponent implements OnInit, ControlValueAccessor, Valid @@ -123,7 +124,7 @@ export class JsonContentComponent implements OnInit, ControlValueAccessor, Valid
123 if (this.contentType) { 124 if (this.contentType) {
124 mode = contentTypesMap.get(this.contentType).code; 125 mode = contentTypesMap.get(this.contentType).code;
125 } 126 }
126 - let editorOptions: Partial<ace.Ace.EditorOptions> = { 127 + let editorOptions: Partial<Ace.EditorOptions> = {
127 mode: `ace/mode/${mode}`, 128 mode: `ace/mode/${mode}`,
128 showGutter: true, 129 showGutter: true,
129 showPrintMargin: false, 130 showPrintMargin: false,
@@ -137,22 +138,27 @@ export class JsonContentComponent implements OnInit, ControlValueAccessor, Valid @@ -137,22 +138,27 @@ export class JsonContentComponent implements OnInit, ControlValueAccessor, Valid
137 }; 138 };
138 139
139 editorOptions = {...editorOptions, ...advancedOptions}; 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 ngOnDestroy(): void { 164 ngOnDestroy(): void {
@@ -16,10 +16,20 @@ @@ -16,10 +16,20 @@
16 import * as React from 'react'; 16 import * as React from 'react';
17 import ThingsboardBaseComponent from './json-form-base-component'; 17 import ThingsboardBaseComponent from './json-form-base-component';
18 import reactCSS from 'reactcss'; 18 import reactCSS from 'reactcss';
19 -import ReactAce from 'react-ace';  
20 import Button from '@material-ui/core/Button'; 19 import Button from '@material-ui/core/Button';
21 import { JsonFormFieldProps, JsonFormFieldState } from '@shared/components/json-form/react/json-form.models'; 20 import { JsonFormFieldProps, JsonFormFieldState } from '@shared/components/json-form/react/json-form.models';
22 import { IEditorProps } from 'react-ace/src/types'; 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 interface ThingsboardAceEditorProps extends JsonFormFieldProps { 34 interface ThingsboardAceEditorProps extends JsonFormFieldProps {
25 mode: string; 35 mode: string;
@@ -153,22 +163,24 @@ class ThingsboardAceEditor extends React.Component<ThingsboardAceEditorProps, Th @@ -153,22 +163,24 @@ class ThingsboardAceEditor extends React.Component<ThingsboardAceEditorProps, Th
153 'Exit fullscreen' : 'Fullscreen'} 163 'Exit fullscreen' : 'Fullscreen'}
154 </Button> 164 </Button>
155 </div> 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 </div> 184 </div>
173 <div className='json-form-error' 185 <div className='json-form-error'
174 style={{opacity: this.props.valid ? '0' : '1'}}>{this.props.error}</div> 186 style={{opacity: this.props.valid ? '0' : '1'}}>{this.props.error}</div>
@@ -16,7 +16,7 @@ @@ -16,7 +16,7 @@
16 16
17 import { Component, ElementRef, forwardRef, Input, OnDestroy, OnInit, ViewChild } from '@angular/core'; 17 import { Component, ElementRef, forwardRef, Input, OnDestroy, OnInit, ViewChild } from '@angular/core';
18 import { ControlValueAccessor, FormControl, NG_VALIDATORS, NG_VALUE_ACCESSOR, Validator } from '@angular/forms'; 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 import { coerceBooleanProperty } from '@angular/cdk/coercion'; 20 import { coerceBooleanProperty } from '@angular/cdk/coercion';
21 import { ActionNotificationHide, ActionNotificationShow } from '@core/notification/notification.actions'; 21 import { ActionNotificationHide, ActionNotificationShow } from '@core/notification/notification.actions';
22 import { Store } from '@ngrx/store'; 22 import { Store } from '@ngrx/store';
@@ -24,6 +24,7 @@ import { AppState } from '@core/core.state'; @@ -24,6 +24,7 @@ import { AppState } from '@core/core.state';
24 import { CancelAnimationFrame, RafService } from '@core/services/raf.service'; 24 import { CancelAnimationFrame, RafService } from '@core/services/raf.service';
25 import { guid } from '@core/utils'; 25 import { guid } from '@core/utils';
26 import { ResizeObserver } from '@juggle/resize-observer'; 26 import { ResizeObserver } from '@juggle/resize-observer';
  27 +import { getAce } from '@shared/models/ace/ace.models';
27 28
28 @Component({ 29 @Component({
29 selector: 'tb-json-object-edit', 30 selector: 'tb-json-object-edit',
@@ -47,7 +48,7 @@ export class JsonObjectEditComponent implements OnInit, ControlValueAccessor, Va @@ -47,7 +48,7 @@ export class JsonObjectEditComponent implements OnInit, ControlValueAccessor, Va
47 @ViewChild('jsonEditor', {static: true}) 48 @ViewChild('jsonEditor', {static: true})
48 jsonEditorElmRef: ElementRef; 49 jsonEditorElmRef: ElementRef;
49 50
50 - private jsonEditor: ace.Ace.Editor; 51 + private jsonEditor: Ace.Editor;
51 private editorsResizeCaf: CancelAnimationFrame; 52 private editorsResizeCaf: CancelAnimationFrame;
52 private editorResize$: ResizeObserver; 53 private editorResize$: ResizeObserver;
53 54
@@ -102,7 +103,7 @@ export class JsonObjectEditComponent implements OnInit, ControlValueAccessor, Va @@ -102,7 +103,7 @@ export class JsonObjectEditComponent implements OnInit, ControlValueAccessor, Va
102 103
103 ngOnInit(): void { 104 ngOnInit(): void {
104 const editorElement = this.jsonEditorElmRef.nativeElement; 105 const editorElement = this.jsonEditorElmRef.nativeElement;
105 - let editorOptions: Partial<ace.Ace.EditorOptions> = { 106 + let editorOptions: Partial<Ace.EditorOptions> = {
106 mode: 'ace/mode/json', 107 mode: 'ace/mode/json',
107 showGutter: true, 108 showGutter: true,
108 showPrintMargin: false, 109 showPrintMargin: false,
@@ -116,19 +117,24 @@ export class JsonObjectEditComponent implements OnInit, ControlValueAccessor, Va @@ -116,19 +117,24 @@ export class JsonObjectEditComponent implements OnInit, ControlValueAccessor, Va
116 }; 117 };
117 118
118 editorOptions = {...editorOptions, ...advancedOptions}; 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 ngOnDestroy(): void { 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,7 +14,7 @@
14 /// limitations under the License. 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 export type tbMetaType = 'object' | 'function' | 'service' | 'property' | 'argument'; 19 export type tbMetaType = 'object' | 'function' | 'service' | 'property' | 'argument';
20 20
@@ -41,7 +41,7 @@ export interface TbEditorCompletion { @@ -41,7 +41,7 @@ export interface TbEditorCompletion {
41 children?: TbEditorCompletions; 41 children?: TbEditorCompletions;
42 } 42 }
43 43
44 -interface TbEditorAceCompletion extends ace.Ace.Completion { 44 +interface TbEditorAceCompletion extends Ace.Completion {
45 isTbEditorAceCompletion: true; 45 isTbEditorAceCompletion: true;
46 snippet: string; 46 snippet: string;
47 description?: string; 47 description?: string;
@@ -50,7 +50,7 @@ interface TbEditorAceCompletion extends ace.Ace.Completion { @@ -50,7 +50,7 @@ interface TbEditorAceCompletion extends ace.Ace.Completion {
50 return?: FunctionArgType; 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 identifierRegexps: RegExp[] = [ 55 identifierRegexps: RegExp[] = [
56 /[a-zA-Z_0-9\$\-\u00A2-\u2000\u2070-\uFFFF.]/ 56 /[a-zA-Z_0-9\$\-\u00A2-\u2000\u2070-\uFFFF.]/
@@ -59,8 +59,8 @@ export class TbEditorCompleter implements ace.Ace.Completer { @@ -59,8 +59,8 @@ export class TbEditorCompleter implements ace.Ace.Completer {
59 constructor(private editorCompletions: TbEditorCompletions) { 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 const result = this.prepareCompletions(prefix); 64 const result = this.prepareCompletions(prefix);
65 if (result) { 65 if (result) {
66 callback(null, result); 66 callback(null, result);
@@ -91,7 +91,7 @@ export class TbEditorCompleter implements ace.Ace.Completer { @@ -91,7 +91,7 @@ export class TbEditorCompleter implements ace.Ace.Completer {
91 return parts; 91 return parts;
92 } 92 }
93 93
94 - private prepareCompletions(prefix: string): ace.Ace.Completion[] { 94 + private prepareCompletions(prefix: string): Ace.Completion[] {
95 const path = this.resolvePath(prefix); 95 const path = this.resolvePath(prefix);
96 if (path !== null) { 96 if (path !== null) {
97 return this.toAceCompletionsList(this.editorCompletions, path); 97 return this.toAceCompletionsList(this.editorCompletions, path);
@@ -100,8 +100,8 @@ export class TbEditorCompleter implements ace.Ace.Completer { @@ -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 let targetCompletions = completions; 105 let targetCompletions = completions;
106 let parentPrefix = ''; 106 let parentPrefix = '';
107 if (parentPath.length) { 107 if (parentPath.length) {
@@ -116,7 +116,7 @@ export class TbEditorCompleter implements ace.Ace.Completer { @@ -116,7 +116,7 @@ export class TbEditorCompleter implements ace.Ace.Completer {
116 return result; 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 const aceCompletion: TbEditorAceCompletion = { 120 const aceCompletion: TbEditorAceCompletion = {
121 isTbEditorAceCompletion: true, 121 isTbEditorAceCompletion: true,
122 snippet: parentPrefix + name, 122 snippet: parentPrefix + name,
@@ -175,7 +175,7 @@ export class TbEditorCompleter implements ace.Ace.Completer { @@ -175,7 +175,7 @@ export class TbEditorCompleter implements ace.Ace.Completer {
175 if (completion.args || completion.return) { 175 if (completion.args || completion.return) {
176 let functionInfoBlock = '<div class="tb-function-info">'; 176 let functionInfoBlock = '<div class="tb-function-info">';
177 if (completion.args) { 177 if (completion.args) {
178 - functionInfoBlock += '<div class="tb-api-title">Parameters</div>' 178 + functionInfoBlock += '<div class="tb-api-title">Parameters</div>';
179 let argsTable = '<table class="tb-api-table"><tbody>'; 179 let argsTable = '<table class="tb-api-table"><tbody>';
180 const strArgs: string[] = []; 180 const strArgs: string[] = [];
181 for (const arg of completion.args) { 181 for (const arg of completion.args) {
@@ -185,7 +185,7 @@ export class TbEditorCompleter implements ace.Ace.Completer { @@ -185,7 +185,7 @@ export class TbEditorCompleter implements ace.Ace.Completer {
185 } 185 }
186 strArg += '</code></td><td>'; 186 strArg += '</code></td><td>';
187 if (arg.type) { 187 if (arg.type) {
188 - strArg += `<code>${arg.type}</code>` 188 + strArg += `<code>${arg.type}</code>`;
189 } 189 }
190 strArg += '</td><td class="arg-description">'; 190 strArg += '</td><td class="arg-description">';
191 if (arg.description) { 191 if (arg.description) {
@@ -75,7 +75,6 @@ @@ -75,7 +75,6 @@
75 import './zone-flags'; 75 import './zone-flags';
76 import 'zone.js/dist/zone'; // Included with Angular CLI. 76 import 'zone.js/dist/zone'; // Included with Angular CLI.
77 import 'core-js/es/array'; 77 import 'core-js/es/array';
78 -import moment from 'moment';  
79 78
80 /*************************************************************************************************** 79 /***************************************************************************************************
81 * APPLICATION IMPORTS 80 * APPLICATION IMPORTS
@@ -87,26 +86,4 @@ import moment from 'moment'; @@ -87,26 +86,4 @@ import moment from 'moment';
87 * WIDGETS IMPORTS 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,7 +14,7 @@
14 /// limitations under the License. 14 /// limitations under the License.
15 /// 15 ///
16 16
17 -import * as L from 'leaflet' 17 +import * as L from 'leaflet';
18 18
19 declare module 'leaflet' { 19 declare module 'leaflet' {
20 20
@@ -34,6 +34,9 @@ @@ -34,6 +34,9 @@
34 "@home/*": ["src/app/modules/home/*"], 34 "@home/*": ["src/app/modules/home/*"],
35 "jszip": [ 35 "jszip": [
36 "node_modules/jszip/dist/jszip.min.js" 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 "lib": [ 42 "lib": [