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