Commit 11db772ea2d753f178faa14e6eee0e2ac830b424

Authored by ArtemHalushko
Committed by GitHub
1 parent c6cf5c43

bugfixes (#2705)

@@ -17,7 +17,7 @@ @@ -17,7 +17,7 @@
17 import { AliasInfo, IAliasController, StateControllerHolder, StateEntityInfo } from '@core/api/widget-api.models'; 17 import { AliasInfo, IAliasController, StateControllerHolder, StateEntityInfo } from '@core/api/widget-api.models';
18 import { forkJoin, Observable, of, ReplaySubject, Subject } from 'rxjs'; 18 import { forkJoin, Observable, of, ReplaySubject, Subject } from 'rxjs';
19 import { DataKey, Datasource, DatasourceType } from '@app/shared/models/widget.models'; 19 import { DataKey, Datasource, DatasourceType } from '@app/shared/models/widget.models';
20 -import { deepClone, isEqual } from '@core/utils'; 20 +import { deepClone, isEqual, createLabelFromDatasource } from '@core/utils';
21 import { EntityService } from '@core/http/entity.service'; 21 import { EntityService } from '@core/http/entity.service';
22 import { UtilsService } from '@core/services/utils.service'; 22 import { UtilsService } from '@core/services/utils.service';
23 import { EntityAliases } from '@shared/models/alias.models'; 23 import { EntityAliases } from '@shared/models/alias.models';
@@ -329,7 +329,7 @@ export class AliasController implements IAliasController { @@ -329,7 +329,7 @@ export class AliasController implements IAliasController {
329 if (!dataKey.pattern) { 329 if (!dataKey.pattern) {
330 dataKey.pattern = deepClone(dataKey.label); 330 dataKey.pattern = deepClone(dataKey.label);
331 } 331 }
332 - dataKey.label = this.utils.createLabelFromDatasource(datasource, dataKey.pattern); 332 + dataKey.label = createLabelFromDatasource(datasource, dataKey.pattern);
333 } 333 }
334 334
335 getInstantAliasInfo(aliasId: string): AliasInfo { 335 getInstantAliasInfo(aliasId: string): AliasInfo {
@@ -20,7 +20,7 @@ @@ -20,7 +20,7 @@
20 import { Inject, Injectable, NgZone } from '@angular/core'; 20 import { Inject, Injectable, NgZone } from '@angular/core';
21 import { WINDOW } from '@core/services/window.service'; 21 import { WINDOW } from '@core/services/window.service';
22 import { ExceptionData } from '@app/shared/models/error.models'; 22 import { ExceptionData } from '@app/shared/models/error.models';
23 -import { deepClone, deleteNullProperties, guid, isDefined, isDefinedAndNotNull, isUndefined } from '@core/utils'; 23 +import { deepClone, deleteNullProperties, guid, isDefined, isDefinedAndNotNull, isUndefined, createLabelFromDatasource } from '@core/utils';
24 import { WindowMessage } from '@shared/models/window-message.model'; 24 import { WindowMessage } from '@shared/models/window-message.model';
25 import { TranslateService } from '@ngx-translate/core'; 25 import { TranslateService } from '@ngx-translate/core';
26 import { customTranslationsPrefix } from '@app/shared/models/constants'; 26 import { customTranslationsPrefix } from '@app/shared/models/constants';
@@ -36,7 +36,7 @@ import { Observable, of, ReplaySubject } from 'rxjs'; @@ -36,7 +36,7 @@ import { Observable, of, ReplaySubject } from 'rxjs';
36 36
37 const varsRegex = /\$\{([^}]*)\}/g; 37 const varsRegex = /\$\{([^}]*)\}/g;
38 38
39 -const predefinedFunctions: {[func: string]: string} = { 39 +const predefinedFunctions: { [func: string]: string } = {
40 Sin: 'return Math.round(1000*Math.sin(time/5000));', 40 Sin: 'return Math.round(1000*Math.sin(time/5000));',
41 Cos: 'return Math.round(1000*Math.cos(time/5000));', 41 Cos: 'return Math.round(1000*Math.cos(time/5000));',
42 Random: 'var value = prevValue + Math.random() * 100 - 50;\n' + 42 Random: 'var value = prevValue + Math.random() * 100 - 50;\n' +
@@ -63,12 +63,12 @@ const defaultAlarmFields: Array<string> = [ @@ -63,12 +63,12 @@ const defaultAlarmFields: Array<string> = [
63 alarmFields.status.keyName 63 alarmFields.status.keyName
64 ]; 64 ];
65 65
66 -const commonMaterialIcons: Array<string> = [ 'more_horiz', 'more_vert', 'open_in_new', 66 +const commonMaterialIcons: Array<string> = ['more_horiz', 'more_vert', 'open_in_new',
67 'visibility', 'play_arrow', 'arrow_back', 'arrow_downward', 67 'visibility', 'play_arrow', 'arrow_back', 'arrow_downward',
68 'arrow_forward', 'arrow_upwards', 'close', 'refresh', 'menu', 'show_chart', 'multiline_chart', 'pie_chart', 'insert_chart', 'people', 68 'arrow_forward', 'arrow_upwards', 'close', 'refresh', 'menu', 'show_chart', 'multiline_chart', 'pie_chart', 'insert_chart', 'people',
69 'person', 'domain', 'devices_other', 'now_widgets', 'dashboards', 'map', 'pin_drop', 'my_location', 'extension', 'search', 69 'person', 'domain', 'devices_other', 'now_widgets', 'dashboards', 'map', 'pin_drop', 'my_location', 'extension', 'search',
70 'settings', 'notifications', 'notifications_active', 'info', 'info_outline', 'warning', 'list', 'file_download', 'import_export', 70 'settings', 'notifications', 'notifications_active', 'info', 'info_outline', 'warning', 'list', 'file_download', 'import_export',
71 - 'share', 'add', 'edit', 'done' ]; 71 + 'share', 'add', 'edit', 'done'];
72 72
73 // @dynamic 73 // @dynamic
74 @Injectable({ 74 @Injectable({
@@ -101,8 +101,8 @@ export class UtilsService { @@ -101,8 +101,8 @@ export class UtilsService {
101 materialIcons: Array<string> = []; 101 materialIcons: Array<string> = [];
102 102
103 constructor(@Inject(WINDOW) private window: Window, 103 constructor(@Inject(WINDOW) private window: Window,
104 - private zone: NgZone,  
105 - private translate: TranslateService) { 104 + private zone: NgZone,
  105 + private translate: TranslateService) {
106 let frame: Element = null; 106 let frame: Element = null;
107 try { 107 try {
108 frame = window.frameElement; 108 frame = window.frameElement;
@@ -302,10 +302,10 @@ export class UtilsService { @@ -302,10 +302,10 @@ export class UtilsService {
302 .split('\n') 302 .split('\n')
303 .filter((codepoint) => codepoint && codepoint.length); 303 .filter((codepoint) => codepoint && codepoint.length);
304 codepointsArray.forEach((codepoint) => { 304 codepointsArray.forEach((codepoint) => {
305 - const values = codepoint.split(' ');  
306 - if (values && values.length === 2) {  
307 - this.materialIcons.push(values[0]);  
308 - } 305 + const values = codepoint.split(' ');
  306 + if (values && values.length === 2) {
  307 + this.materialIcons.push(values[0]);
  308 + }
309 }); 309 });
310 materialIconsSubject.next(this.materialIcons); 310 materialIconsSubject.next(this.materialIcons);
311 }); 311 });
@@ -360,12 +360,12 @@ export class UtilsService { @@ -360,12 +360,12 @@ export class UtilsService {
360 } 360 }
361 361
362 public createAdditionalDataKey(dataKey: DataKey, datasource: Datasource, timeUnit: string, 362 public createAdditionalDataKey(dataKey: DataKey, datasource: Datasource, timeUnit: string,
363 - datasources: Datasource[], additionalKeysNumber: number): DataKey { 363 + datasources: Datasource[], additionalKeysNumber: number): DataKey {
364 const additionalDataKey = deepClone(dataKey); 364 const additionalDataKey = deepClone(dataKey);
365 if (dataKey.settings.comparisonSettings.comparisonValuesLabel) { 365 if (dataKey.settings.comparisonSettings.comparisonValuesLabel) {
366 - additionalDataKey.label = this.createLabelFromDatasource(datasource, dataKey.settings.comparisonSettings.comparisonValuesLabel); 366 + additionalDataKey.label = createLabelFromDatasource(datasource, dataKey.settings.comparisonSettings.comparisonValuesLabel);
367 } else { 367 } else {
368 - additionalDataKey.label = dataKey.label + ' ' + this.translate.instant('legend.comparison-time-ago.'+timeUnit); 368 + additionalDataKey.label = dataKey.label + ' ' + this.translate.instant('legend.comparison-time-ago.' + timeUnit);
369 } 369 }
370 additionalDataKey.pattern = additionalDataKey.label; 370 additionalDataKey.pattern = additionalDataKey.label;
371 if (dataKey.settings.comparisonSettings.color) { 371 if (dataKey.settings.comparisonSettings.color) {
@@ -380,30 +380,7 @@ export class UtilsService { @@ -380,30 +380,7 @@ export class UtilsService {
380 } 380 }
381 381
382 public createLabelFromDatasource(datasource: Datasource, pattern: string) { 382 public createLabelFromDatasource(datasource: Datasource, pattern: string) {
383 - let label = pattern;  
384 - if (!datasource) {  
385 - return label;  
386 - }  
387 - let match = varsRegex.exec(pattern);  
388 - while (match !== null) {  
389 - const variable = match[0];  
390 - const variableName = match[1];  
391 - if (variableName === 'dsName') {  
392 - label = label.split(variable).join(datasource.name);  
393 - } else if (variableName === 'entityName') {  
394 - label = label.split(variable).join(datasource.entityName);  
395 - } else if (variableName === 'deviceName') {  
396 - label = label.split(variable).join(datasource.entityName);  
397 - } else if (variableName === 'entityLabel') {  
398 - label = label.split(variable).join(datasource.entityLabel || datasource.entityName);  
399 - } else if (variableName === 'aliasName') {  
400 - label = label.split(variable).join(datasource.aliasName);  
401 - } else if (variableName === 'entityDescription') {  
402 - label = label.split(variable).join(datasource.entityDescription);  
403 - }  
404 - match = varsRegex.exec(pattern);  
405 - }  
406 - return label; 383 + return createLabelFromDatasource(datasource, pattern);
407 } 384 }
408 385
409 public generateColors(datasources: Array<Datasource>) { 386 public generateColors(datasources: Array<Datasource>) {
@@ -456,7 +433,7 @@ export class UtilsService { @@ -456,7 +433,7 @@ export class UtilsService {
456 params = urlQueryString + '&' + newParam; 433 params = urlQueryString + '&' + newParam;
457 } 434 }
458 } else if (newParam) { 435 } else if (newParam) {
459 - params = '?' + newParam; 436 + params = '?' + newParam;
460 } 437 }
461 this.window.history.replaceState({}, '', baseUrl + params); 438 this.window.history.replaceState({}, '', baseUrl + params);
462 } 439 }
@@ -18,6 +18,7 @@ import _ from 'lodash'; @@ -18,6 +18,7 @@ import _ from 'lodash';
18 import { Observable, Subject, fromEvent, of } from 'rxjs'; 18 import { Observable, Subject, fromEvent, of } from 'rxjs';
19 import { finalize, share, map } from 'rxjs/operators'; 19 import { finalize, share, map } from 'rxjs/operators';
20 import base64js from 'base64-js'; 20 import base64js from 'base64-js';
  21 +import { Datasource } from '@app/shared/models/widget.models';
21 22
22 export function onParentScrollOrWindowResize(el: Node): Observable<Event> { 23 export function onParentScrollOrWindowResize(el: Node): Observable<Event> {
23 const scrollSubject = new Subject<Event>(); 24 const scrollSubject = new Subject<Event>();
@@ -435,6 +436,34 @@ export function imageLoader(imageUrl: string): Observable<HTMLImageElement> { @@ -435,6 +436,34 @@ export function imageLoader(imageUrl: string): Observable<HTMLImageElement> {
435 return imageLoad$; 436 return imageLoad$;
436 } 437 }
437 438
  439 +export function createLabelFromDatasource(datasource: Datasource, pattern: string) {
  440 + const varsRegex = /\$\{([^}]*)\}/g;
  441 + let label = pattern;
  442 + if (!datasource) {
  443 + return label;
  444 + }
  445 + let match = varsRegex.exec(pattern);
  446 + while (match !== null) {
  447 + const variable = match[0];
  448 + const variableName = match[1];
  449 + if (variableName === 'dsName') {
  450 + label = label.split(variable).join(datasource.name);
  451 + } else if (variableName === 'entityName') {
  452 + label = label.split(variable).join(datasource.entityName);
  453 + } else if (variableName === 'deviceName') {
  454 + label = label.split(variable).join(datasource.entityName);
  455 + } else if (variableName === 'entityLabel') {
  456 + label = label.split(variable).join(datasource.entityLabel || datasource.entityName);
  457 + } else if (variableName === 'aliasName') {
  458 + label = label.split(variable).join(datasource.aliasName);
  459 + } else if (variableName === 'entityDescription') {
  460 + label = label.split(variable).join(datasource.entityDescription);
  461 + }
  462 + match = varsRegex.exec(pattern);
  463 + }
  464 + return label;
  465 +}
  466 +
438 const imageAspectMap = {}; 467 const imageAspectMap = {};
439 468
440 export function aspectCache(imageUrl: string): Observable<number> { 469 export function aspectCache(imageUrl: string): Observable<number> {
@@ -452,7 +481,6 @@ export function aspectCache(imageUrl: string): Observable<number> { @@ -452,7 +481,6 @@ export function aspectCache(imageUrl: string): Observable<number> {
452 } 481 }
453 } 482 }
454 483
455 -  
456 export function parseArray(input: any[]): any[] { 484 export function parseArray(input: any[]): any[] {
457 return _(input).groupBy(el => el?.datasource?.entityName) 485 return _(input).groupBy(el => el?.datasource?.entityName)
458 .values().value().map((entityArray, dsIndex) => 486 .values().value().map((entityArray, dsIndex) =>
@@ -523,15 +551,18 @@ export function parseFunction(source: any, params: string[] = ['def']): Function @@ -523,15 +551,18 @@ export function parseFunction(source: any, params: string[] = ['def']): Function
523 return res; 551 return res;
524 } 552 }
525 553
526 -export function parseTemplate(template: string, data: object, translateFn?: (key: string) => string) { 554 +export function parseTemplate(template: string, data: { $datasource?: Datasource, [key: string]: any },
  555 + translateFn?: (key: string) => string) {
527 let res = ''; 556 let res = '';
528 try { 557 try {
529 if (template.match(/<link-act/g)) { 558 if (template.match(/<link-act/g)) {
530 - template = template.replace(/<link-act/g, '<a').replace(/link-act>/g, 'a>').replace(/name=(\'|")(.*?)(\'|")/g, `class='tb-custom-action' id='$2'`); 559 + template = template.replace(/<link-act/g, '<a').replace(/link-act>/g, 'a>')
  560 + .replace(/name=(\'|")(.*?)(\'|")/g, `class='tb-custom-action' id='$2'`);
531 } 561 }
532 if (translateFn) { 562 if (translateFn) {
533 template = translateFn(template); 563 template = translateFn(template);
534 } 564 }
  565 + template = createLabelFromDatasource(data.$datasource, template);
535 const formatted = template.match(/\$\{([^}]*)\:\d*\}/g); 566 const formatted = template.match(/\$\{([^}]*)\:\d*\}/g);
536 if (formatted) 567 if (formatted)
537 formatted.forEach(value => { 568 formatted.forEach(value => {
@@ -33,7 +33,7 @@ import { Datasource, WidgetActionDescriptor, WidgetConfig } from '@shared/models @@ -33,7 +33,7 @@ import { Datasource, WidgetActionDescriptor, WidgetConfig } from '@shared/models
33 import { IWidgetSubscription } from '@core/api/widget-api.models'; 33 import { IWidgetSubscription } from '@core/api/widget-api.models';
34 import { UtilsService } from '@core/services/utils.service'; 34 import { UtilsService } from '@core/services/utils.service';
35 import { TranslateService } from '@ngx-translate/core'; 35 import { TranslateService } from '@ngx-translate/core';
36 -import { deepClone, isDefined, isNumber } from '@core/utils'; 36 +import { deepClone, isDefined, isNumber, createLabelFromDatasource } from '@core/utils';
37 import cssjs from '@core/css/css'; 37 import cssjs from '@core/css/css';
38 import { PageLink } from '@shared/models/page/page-link'; 38 import { PageLink } from '@shared/models/page/page-link';
39 import { Direction, SortOrder, sortOrderFromString } from '@shared/models/page/sort-order'; 39 import { Direction, SortOrder, sortOrderFromString } from '@shared/models/page/sort-order';
@@ -282,7 +282,7 @@ export class AlarmsTableWidgetComponent extends PageComponent implements OnInit, @@ -282,7 +282,7 @@ export class AlarmsTableWidgetComponent extends PageComponent implements OnInit,
282 alarmsTitle = this.translate.instant('alarm.alarms'); 282 alarmsTitle = this.translate.instant('alarm.alarms');
283 } 283 }
284 284
285 - this.ctx.widgetTitle = this.utils.createLabelFromDatasource(this.alarmSource, alarmsTitle); 285 + this.ctx.widgetTitle = createLabelFromDatasource(this.alarmSource, alarmsTitle);
286 286
287 this.enableSelection = isDefined(this.settings.enableSelection) ? this.settings.enableSelection : true; 287 this.enableSelection = isDefined(this.settings.enableSelection) ? this.settings.enableSelection : true;
288 if (!this.allowAcknowledgment && !this.allowClear) { 288 if (!this.allowAcknowledgment && !this.allowClear) {
@@ -39,7 +39,7 @@ import { @@ -39,7 +39,7 @@ import {
39 import { IWidgetSubscription } from '@core/api/widget-api.models'; 39 import { IWidgetSubscription } from '@core/api/widget-api.models';
40 import { UtilsService } from '@core/services/utils.service'; 40 import { UtilsService } from '@core/services/utils.service';
41 import { TranslateService } from '@ngx-translate/core'; 41 import { TranslateService } from '@ngx-translate/core';
42 -import { deepClone, isDefined, isNumber } from '@core/utils'; 42 +import { deepClone, isDefined, isNumber, createLabelFromDatasource } from '@core/utils';
43 import cssjs from '@core/css/css'; 43 import cssjs from '@core/css/css';
44 import { PageLink } from '@shared/models/page/page-link'; 44 import { PageLink } from '@shared/models/page/page-link';
45 import { Direction, SortOrder, sortOrderFromString } from '@shared/models/page/sort-order'; 45 import { Direction, SortOrder, sortOrderFromString } from '@shared/models/page/sort-order';
@@ -210,7 +210,7 @@ export class EntitiesTableWidgetComponent extends PageComponent implements OnIni @@ -210,7 +210,7 @@ export class EntitiesTableWidgetComponent extends PageComponent implements OnIni
210 } 210 }
211 211
212 const datasource = this.subscription.datasources[0]; 212 const datasource = this.subscription.datasources[0];
213 - this.ctx.widgetTitle = this.utils.createLabelFromDatasource(datasource, entitiesTitle); 213 + this.ctx.widgetTitle = createLabelFromDatasource(datasource, entitiesTitle);
214 214
215 this.searchAction.show = isDefined(this.settings.enableSearch) ? this.settings.enableSearch : true; 215 this.searchAction.show = isDefined(this.settings.enableSearch) ? this.settings.enableSearch : true;
216 this.displayPagination = isDefined(this.settings.displayPagination) ? this.settings.displayPagination : true; 216 this.displayPagination = isDefined(this.settings.displayPagination) ? this.settings.displayPagination : true;
@@ -26,6 +26,7 @@ import { filter } from 'rxjs/operators'; @@ -26,6 +26,7 @@ import { filter } from 'rxjs/operators';
26 import { Polyline } from './polyline'; 26 import { Polyline } from './polyline';
27 import { Polygon } from './polygon'; 27 import { Polygon } from './polygon';
28 import { DatasourceData } from '@app/shared/models/widget.models'; 28 import { DatasourceData } from '@app/shared/models/widget.models';
  29 +import { safeExecute } from '@app/core/utils';
29 30
30 export default abstract class LeafletMap { 31 export default abstract class LeafletMap {
31 32
@@ -87,12 +88,14 @@ export default abstract class LeafletMap { @@ -87,12 +88,14 @@ export default abstract class LeafletMap {
87 if (this.options.draggableMarker) { 88 if (this.options.draggableMarker) {
88 let mousePositionOnMap: L.LatLng; 89 let mousePositionOnMap: L.LatLng;
89 let addMarker: L.Control; 90 let addMarker: L.Control;
90 - this.map.on('mouseup', (e: L.LeafletMouseEvent) => { 91 + this.map.on('mousemove', (e: L.LeafletMouseEvent) => {
91 mousePositionOnMap = e.latlng; 92 mousePositionOnMap = e.latlng;
92 }); 93 });
93 const dragListener = (e: L.DragEndEvent) => { 94 const dragListener = (e: L.DragEndEvent) => {
94 if (e.type === 'dragend' && mousePositionOnMap) { 95 if (e.type === 'dragend' && mousePositionOnMap) {
95 - const newMarker = L.marker(mousePositionOnMap).addTo(this.map); 96 + const icon = new L.Icon.Default();
  97 + icon.options.shadowSize = [0, 0];
  98 + const newMarker = L.marker(mousePositionOnMap, { icon }).addTo(this.map);
96 const datasourcesList = document.createElement('div'); 99 const datasourcesList = document.createElement('div');
97 const customLatLng = this.convertToCustomFormat(mousePositionOnMap); 100 const customLatLng = this.convertToCustomFormat(mousePositionOnMap);
98 this.datasources.forEach(ds => { 101 this.datasources.forEach(ds => {
@@ -195,15 +198,18 @@ export default abstract class LeafletMap { @@ -195,15 +198,18 @@ export default abstract class LeafletMap {
195 198
196 fitBounds(bounds: LatLngBounds, useDefaultZoom = false, padding?: LatLngTuple) { 199 fitBounds(bounds: LatLngBounds, useDefaultZoom = false, padding?: LatLngTuple) {
197 if (bounds.isValid()) { 200 if (bounds.isValid()) {
198 - if ((!this.options.fitMapBounds || useDefaultZoom) && this.options.defaultZoomLevel) { 201 + if ((!this.options.fitMapBounds || this.options.useDefaultCenterPosition) && this.options.defaultZoomLevel) {
199 this.map.setZoom(this.options.defaultZoomLevel, { animate: false }); 202 this.map.setZoom(this.options.defaultZoomLevel, { animate: false });
200 - this.map.panTo(bounds.getCenter(), { animate: false }); 203 + this.map.panTo(this.options.defaultCenterPosition, { animate: false });
201 } else { 204 } else {
202 this.map.once('zoomend', () => { 205 this.map.once('zoomend', () => {
203 if (!this.options.defaultZoomLevel && this.map.getZoom() > this.options.minZoomLevel) { 206 if (!this.options.defaultZoomLevel && this.map.getZoom() > this.options.minZoomLevel) {
204 this.map.setZoom(this.options.minZoomLevel, { animate: false }); 207 this.map.setZoom(this.options.minZoomLevel, { animate: false });
205 } 208 }
206 }); 209 });
  210 + if (this.options.useDefaultCenterPosition) {
  211 + bounds = bounds.extend(this.options.defaultCenterPosition);
  212 + }
207 this.map.fitBounds(bounds, { padding: padding || [50, 50], animate: false }); 213 this.map.fitBounds(bounds, { padding: padding || [50, 50], animate: false });
208 } 214 }
209 this.bounds = bounds; 215 this.bounds = bounds;
@@ -231,8 +237,16 @@ export default abstract class LeafletMap { @@ -231,8 +237,16 @@ export default abstract class LeafletMap {
231 updateMarkers(markersData) { 237 updateMarkers(markersData) {
232 markersData.filter(mdata => !!this.convertPosition(mdata)).forEach(data => { 238 markersData.filter(mdata => !!this.convertPosition(mdata)).forEach(data => {
233 if (data.rotationAngle || data.rotationAngle === 0) { 239 if (data.rotationAngle || data.rotationAngle === 0) {
  240 + const currentImage = this.options.useMarkerImageFunction ?
  241 + safeExecute(this.options.markerImageFunction,
  242 + [data, this.options.markerImages, markersData, data.dsIndex]) : this.options.currentImage;
  243 + const style = currentImage ? 'background-image: url(' + currentImage.url + ');' : '';
234 this.options.icon = L.divIcon({ 244 this.options.icon = L.divIcon({
235 - html: `<div class="arrow" style="transform: translate(-10px, -10px) rotate(${data.rotationAngle}deg);"><div>` 245 + html: `<div class="arrow"
  246 + style="transform: translate(-10px, -10px);
  247 + ${style}
  248 + rotate(${data.rotationAngle}deg);
  249 + "><div>`
236 }) 250 })
237 } 251 }
238 else { 252 else {
@@ -335,31 +349,28 @@ export default abstract class LeafletMap { @@ -335,31 +349,28 @@ export default abstract class LeafletMap {
335 data.data = JSON.parse(data.data[0][1]) as LatLngTuple[]; 349 data.data = JSON.parse(data.data[0][1]) as LatLngTuple[];
336 } 350 }
337 if (this.polygons.get(data.datasource.entityName)) { 351 if (this.polygons.get(data.datasource.entityName)) {
338 - this.updatePolygon(data.datasource.entityName, data.data, polyData, this.options); 352 + this.updatePolygon(data, polyData, this.options);
339 } 353 }
340 else { 354 else {
341 - this.createPolygon(data.datasource.entityName, data.data, polyData, this.options); 355 + this.createPolygon(data, polyData, this.options);
342 } 356 }
343 } 357 }
344 }); 358 });
345 } 359 }
346 360
347 - createPolygon(key: string, data: LatLngTuple[], dataSources: DatasourceData[], settings: PolygonSettings) { 361 + createPolygon(polyData: DatasourceData, dataSources: DatasourceData[], settings: PolygonSettings) {
348 this.ready$.subscribe(() => { 362 this.ready$.subscribe(() => {
349 - const polygon = new Polygon(this.map, data, dataSources, settings); 363 + const polygon = new Polygon(this.map, polyData, dataSources, settings);
350 const bounds = this.bounds.extend(polygon.leafletPoly.getBounds()); 364 const bounds = this.bounds.extend(polygon.leafletPoly.getBounds());
351 - if (bounds.isValid()) {  
352 - this.map.fitBounds(bounds);  
353 - this.bounds = bounds;  
354 - }  
355 - this.polygons.set(key, polygon); 365 + this.fitBounds(bounds);
  366 + this.polygons.set(polyData.datasource.entityName, polygon);
356 }); 367 });
357 } 368 }
358 369
359 - updatePolygon(key: string, data: LatLngTuple[], dataSources: DatasourceData[], settings: PolygonSettings) { 370 + updatePolygon(polyData: DatasourceData, dataSources: DatasourceData[], settings: PolygonSettings) {
360 this.ready$.subscribe(() => { 371 this.ready$.subscribe(() => {
361 - const poly = this.polygons.get(key);  
362 - poly.updatePolygon(data, dataSources, settings); 372 + const poly = this.polygons.get(polyData.datasource.entityName);
  373 + poly.updatePolygon(polyData.data, dataSources, settings);
363 this.fitBounds(poly.leafletPoly.getBounds()); 374 this.fitBounds(poly.leafletPoly.getBounds());
364 }); 375 });
365 } 376 }
@@ -111,9 +111,13 @@ export type PolygonSettings = { @@ -111,9 +111,13 @@ export type PolygonSettings = {
111 polygonStrokeWeight: number; 111 polygonStrokeWeight: number;
112 polygonStrokeColor: string; 112 polygonStrokeColor: string;
113 polygonColor: string; 113 polygonColor: string;
  114 + showPolygonTooltip: boolean;
114 autocloseTooltip: boolean; 115 autocloseTooltip: boolean;
  116 + tooltipFunction: GenericFunction;
115 showTooltipAction: string; 117 showTooltipAction: string;
116 tooltipAction: object; 118 tooltipAction: object;
  119 + tooltipPattern: string;
  120 + useTooltipFunction: boolean;
117 polygonClick: { [name: string]: actionsHandler }; 121 polygonClick: { [name: string]: actionsHandler };
118 polygonColorFunction?: GenericFunction; 122 polygonColorFunction?: GenericFunction;
119 } 123 }
@@ -16,8 +16,9 @@ @@ -16,8 +16,9 @@
16 16
17 import L, { LatLngExpression, LatLngTuple } from 'leaflet'; 17 import L, { LatLngExpression, LatLngTuple } from 'leaflet';
18 import { createTooltip } from './maps-utils'; 18 import { createTooltip } from './maps-utils';
19 -import { PolygonSettings } from './map-models'; 19 +import { PolygonSettings, FormattedData } from './map-models';
20 import { DatasourceData } from '@app/shared/models/widget.models'; 20 import { DatasourceData } from '@app/shared/models/widget.models';
  21 +import { safeExecute, parseWithTranslation } from '@app/core/utils';
21 22
22 export class Polygon { 23 export class Polygon {
23 24
@@ -26,8 +27,8 @@ export class Polygon { @@ -26,8 +27,8 @@ export class Polygon {
26 data; 27 data;
27 dataSources; 28 dataSources;
28 29
29 - constructor(public map, coordinates, dataSources, settings: PolygonSettings, onClickListener?) {  
30 - this.leafletPoly = L.polygon(coordinates, { 30 + constructor(public map, polyData: DatasourceData, dataSources, private settings: PolygonSettings, onClickListener?) {
  31 + this.leafletPoly = L.polygon(polyData.data, {
31 fill: true, 32 fill: true,
32 fillColor: settings.polygonColor, 33 fillColor: settings.polygonColor,
33 color: settings.polygonStrokeColor, 34 color: settings.polygonStrokeColor,
@@ -35,19 +36,29 @@ export class Polygon { @@ -35,19 +36,29 @@ export class Polygon {
35 fillOpacity: settings.polygonOpacity, 36 fillOpacity: settings.polygonOpacity,
36 opacity: settings.polygonStrokeOpacity 37 opacity: settings.polygonStrokeOpacity
37 }).addTo(this.map); 38 }).addTo(this.map);
38 -  
39 - if (settings.showTooltip) { 39 + this.dataSources = dataSources;
  40 + this.data = polyData;
  41 + if (settings.showPolygonTooltip) {
40 this.tooltip = createTooltip(this.leafletPoly, settings); 42 this.tooltip = createTooltip(this.leafletPoly, settings);
  43 + this.updateTooltip(polyData);
41 } 44 }
42 if (onClickListener) { 45 if (onClickListener) {
43 this.leafletPoly.on('click', onClickListener); 46 this.leafletPoly.on('click', onClickListener);
44 } 47 }
45 } 48 }
46 49
  50 + updateTooltip(data: DatasourceData) {
  51 + const pattern = this.settings.useTooltipFunction ?
  52 + safeExecute(this.settings.tooltipFunction, [this.data, this.dataSources, this.data.dsIndex]) : this.settings.tooltipPattern;
  53 + this.tooltip.setContent(parseWithTranslation.parseTemplate(pattern, data, true));
  54 + }
  55 +
47 updatePolygon(data: LatLngTuple[], dataSources: DatasourceData[], settings: PolygonSettings) { 56 updatePolygon(data: LatLngTuple[], dataSources: DatasourceData[], settings: PolygonSettings) {
48 this.data = data; 57 this.data = data;
49 this.dataSources = dataSources; 58 this.dataSources = dataSources;
50 this.leafletPoly.setLatLngs(data); 59 this.leafletPoly.setLatLngs(data);
  60 + if (settings.showPolygonTooltip)
  61 + this.updateTooltip(this.data);
51 this.updatePolygonColor(settings); 62 this.updatePolygonColor(settings);
52 } 63 }
53 64
@@ -477,6 +477,11 @@ export const mapPolygonSchema = @@ -477,6 +477,11 @@ export const mapPolygonSchema =
477 type: 'number', 477 type: 'number',
478 default: 1 478 default: 1
479 }, 479 },
  480 + showPolygonTooltip: {
  481 + title: 'Show polygon tooltip',
  482 + type: 'boolean',
  483 + default: false
  484 + },
480 usePolygonColorFunction: { 485 usePolygonColorFunction: {
481 title: 'Use polygon color function', 486 title: 'Use polygon color function',
482 type: 'boolean', 487 type: 'boolean',
@@ -501,7 +506,7 @@ export const mapPolygonSchema = @@ -501,7 +506,7 @@ export const mapPolygonSchema =
501 key: 'polygonStrokeColor', 506 key: 'polygonStrokeColor',
502 type: 'color' 507 type: 'color'
503 }, 508 },
504 - 'polygonStrokeOpacity', 'polygonStrokeWeight', 'usePolygonColorFunction', 509 + 'polygonStrokeOpacity', 'polygonStrokeWeight', 'usePolygonColorFunction', 'showPolygonTooltip',
505 { 510 {
506 key: 'polygonColorFunction', 511 key: 'polygonColorFunction',
507 type: 'javascript' 512 type: 'javascript'
@@ -1137,7 +1142,7 @@ export const tripAnimationSchema = { @@ -1137,7 +1142,7 @@ export const tripAnimationSchema = {
1137 rotationAngle: { 1142 rotationAngle: {
1138 title: 'Set additional rotation angle for marker (deg)', 1143 title: 'Set additional rotation angle for marker (deg)',
1139 type: 'number', 1144 type: 'number',
1140 - default: 180 1145 + default: 0
1141 }, 1146 },
1142 useMarkerImageFunction: { 1147 useMarkerImageFunction: {
1143 title: 'Use marker image function', 1148 title: 'Use marker image function',
@@ -24,7 +24,7 @@ import { UtilsService } from '@core/services/utils.service'; @@ -24,7 +24,7 @@ import { UtilsService } from '@core/services/utils.service';
24 import { TranslateService } from '@ngx-translate/core'; 24 import { TranslateService } from '@ngx-translate/core';
25 import { DataKey, Datasource, DatasourceData, DatasourceType, WidgetConfig } from '@shared/models/widget.models'; 25 import { DataKey, Datasource, DatasourceData, DatasourceType, WidgetConfig } from '@shared/models/widget.models';
26 import { IWidgetSubscription } from '@core/api/widget-api.models'; 26 import { IWidgetSubscription } from '@core/api/widget-api.models';
27 -import { isDefined, isEqual, isUndefined } from '@core/utils'; 27 +import { isDefined, isEqual, isUndefined, createLabelFromDatasource } from '@core/utils';
28 import { EntityType } from '@shared/models/entity-type.models'; 28 import { EntityType } from '@shared/models/entity-type.models';
29 import * as _moment from 'moment'; 29 import * as _moment from 'moment';
30 import { FormBuilder, FormGroup, ValidatorFn, Validators } from '@angular/forms'; 30 import { FormBuilder, FormGroup, ValidatorFn, Validators } from '@angular/forms';
@@ -331,7 +331,7 @@ export class MultipleInputWidgetComponent extends PageComponent implements OnIni @@ -331,7 +331,7 @@ export class MultipleInputWidgetComponent extends PageComponent implements OnIni
331 } 331 }
332 332
333 public getGroupTitle(datasource: Datasource): string { 333 public getGroupTitle(datasource: Datasource): string {
334 - return this.utils.createLabelFromDatasource(datasource, this.settings.groupTitle); 334 + return createLabelFromDatasource(datasource, this.settings.groupTitle);
335 } 335 }
336 336
337 public visibleKeys(source: MultipleInputWidgetSource): MultipleInputWidgetDataKey[] { 337 public visibleKeys(source: MultipleInputWidgetSource): MultipleInputWidgetDataKey[] {