From 11db772ea2d753f178faa14e6eee0e2ac830b424 Mon Sep 17 00:00:00 2001 From: ArtemHalushko <61501795+ArtemHalushko@users.noreply.github.com> Date: Mon, 4 May 2020 11:40:54 +0300 Subject: [PATCH] bugfixes (#2705) --- ui-ngx/src/app/core/api/alias-controller.ts | 4 +- ui-ngx/src/app/core/services/utils.service.ts | 53 ++++++------------- ui-ngx/src/app/core/utils.ts | 37 +++++++++++-- .../lib/alarms-table-widget.component.ts | 4 +- .../lib/entities-table-widget.component.ts | 4 +- .../components/widget/lib/maps/leaflet-map.ts | 45 ++++++++++------ .../components/widget/lib/maps/map-models.ts | 4 ++ .../components/widget/lib/maps/polygon.ts | 21 ++++++-- .../components/widget/lib/maps/schemes.ts | 9 +++- .../lib/multiple-input-widget.component.ts | 4 +- 10 files changed, 112 insertions(+), 73 deletions(-) diff --git a/ui-ngx/src/app/core/api/alias-controller.ts b/ui-ngx/src/app/core/api/alias-controller.ts index 848da23861..095335839c 100644 --- a/ui-ngx/src/app/core/api/alias-controller.ts +++ b/ui-ngx/src/app/core/api/alias-controller.ts @@ -17,7 +17,7 @@ import { AliasInfo, IAliasController, StateControllerHolder, StateEntityInfo } from '@core/api/widget-api.models'; import { forkJoin, Observable, of, ReplaySubject, Subject } from 'rxjs'; import { DataKey, Datasource, DatasourceType } from '@app/shared/models/widget.models'; -import { deepClone, isEqual } from '@core/utils'; +import { deepClone, isEqual, createLabelFromDatasource } from '@core/utils'; import { EntityService } from '@core/http/entity.service'; import { UtilsService } from '@core/services/utils.service'; import { EntityAliases } from '@shared/models/alias.models'; @@ -329,7 +329,7 @@ export class AliasController implements IAliasController { if (!dataKey.pattern) { dataKey.pattern = deepClone(dataKey.label); } - dataKey.label = this.utils.createLabelFromDatasource(datasource, dataKey.pattern); + dataKey.label = createLabelFromDatasource(datasource, dataKey.pattern); } getInstantAliasInfo(aliasId: string): AliasInfo { diff --git a/ui-ngx/src/app/core/services/utils.service.ts b/ui-ngx/src/app/core/services/utils.service.ts index 24e2c81d03..01341c0941 100644 --- a/ui-ngx/src/app/core/services/utils.service.ts +++ b/ui-ngx/src/app/core/services/utils.service.ts @@ -20,7 +20,7 @@ import { Inject, Injectable, NgZone } from '@angular/core'; import { WINDOW } from '@core/services/window.service'; import { ExceptionData } from '@app/shared/models/error.models'; -import { deepClone, deleteNullProperties, guid, isDefined, isDefinedAndNotNull, isUndefined } from '@core/utils'; +import { deepClone, deleteNullProperties, guid, isDefined, isDefinedAndNotNull, isUndefined, createLabelFromDatasource } from '@core/utils'; import { WindowMessage } from '@shared/models/window-message.model'; import { TranslateService } from '@ngx-translate/core'; import { customTranslationsPrefix } from '@app/shared/models/constants'; @@ -36,7 +36,7 @@ import { Observable, of, ReplaySubject } from 'rxjs'; const varsRegex = /\$\{([^}]*)\}/g; -const predefinedFunctions: {[func: string]: string} = { +const predefinedFunctions: { [func: string]: string } = { Sin: 'return Math.round(1000*Math.sin(time/5000));', Cos: 'return Math.round(1000*Math.cos(time/5000));', Random: 'var value = prevValue + Math.random() * 100 - 50;\n' + @@ -63,12 +63,12 @@ const defaultAlarmFields: Array = [ alarmFields.status.keyName ]; -const commonMaterialIcons: Array = [ 'more_horiz', 'more_vert', 'open_in_new', +const commonMaterialIcons: Array = ['more_horiz', 'more_vert', 'open_in_new', 'visibility', 'play_arrow', 'arrow_back', 'arrow_downward', 'arrow_forward', 'arrow_upwards', 'close', 'refresh', 'menu', 'show_chart', 'multiline_chart', 'pie_chart', 'insert_chart', 'people', 'person', 'domain', 'devices_other', 'now_widgets', 'dashboards', 'map', 'pin_drop', 'my_location', 'extension', 'search', 'settings', 'notifications', 'notifications_active', 'info', 'info_outline', 'warning', 'list', 'file_download', 'import_export', - 'share', 'add', 'edit', 'done' ]; + 'share', 'add', 'edit', 'done']; // @dynamic @Injectable({ @@ -101,8 +101,8 @@ export class UtilsService { materialIcons: Array = []; constructor(@Inject(WINDOW) private window: Window, - private zone: NgZone, - private translate: TranslateService) { + private zone: NgZone, + private translate: TranslateService) { let frame: Element = null; try { frame = window.frameElement; @@ -302,10 +302,10 @@ export class UtilsService { .split('\n') .filter((codepoint) => codepoint && codepoint.length); codepointsArray.forEach((codepoint) => { - const values = codepoint.split(' '); - if (values && values.length === 2) { - this.materialIcons.push(values[0]); - } + const values = codepoint.split(' '); + if (values && values.length === 2) { + this.materialIcons.push(values[0]); + } }); materialIconsSubject.next(this.materialIcons); }); @@ -360,12 +360,12 @@ export class UtilsService { } public createAdditionalDataKey(dataKey: DataKey, datasource: Datasource, timeUnit: string, - datasources: Datasource[], additionalKeysNumber: number): DataKey { + datasources: Datasource[], additionalKeysNumber: number): DataKey { const additionalDataKey = deepClone(dataKey); if (dataKey.settings.comparisonSettings.comparisonValuesLabel) { - additionalDataKey.label = this.createLabelFromDatasource(datasource, dataKey.settings.comparisonSettings.comparisonValuesLabel); + additionalDataKey.label = createLabelFromDatasource(datasource, dataKey.settings.comparisonSettings.comparisonValuesLabel); } else { - additionalDataKey.label = dataKey.label + ' ' + this.translate.instant('legend.comparison-time-ago.'+timeUnit); + additionalDataKey.label = dataKey.label + ' ' + this.translate.instant('legend.comparison-time-ago.' + timeUnit); } additionalDataKey.pattern = additionalDataKey.label; if (dataKey.settings.comparisonSettings.color) { @@ -380,30 +380,7 @@ export class UtilsService { } public createLabelFromDatasource(datasource: Datasource, pattern: string) { - let label = pattern; - if (!datasource) { - return label; - } - let match = varsRegex.exec(pattern); - while (match !== null) { - const variable = match[0]; - const variableName = match[1]; - if (variableName === 'dsName') { - label = label.split(variable).join(datasource.name); - } else if (variableName === 'entityName') { - label = label.split(variable).join(datasource.entityName); - } else if (variableName === 'deviceName') { - label = label.split(variable).join(datasource.entityName); - } else if (variableName === 'entityLabel') { - label = label.split(variable).join(datasource.entityLabel || datasource.entityName); - } else if (variableName === 'aliasName') { - label = label.split(variable).join(datasource.aliasName); - } else if (variableName === 'entityDescription') { - label = label.split(variable).join(datasource.entityDescription); - } - match = varsRegex.exec(pattern); - } - return label; + return createLabelFromDatasource(datasource, pattern); } public generateColors(datasources: Array) { @@ -456,7 +433,7 @@ export class UtilsService { params = urlQueryString + '&' + newParam; } } else if (newParam) { - params = '?' + newParam; + params = '?' + newParam; } this.window.history.replaceState({}, '', baseUrl + params); } diff --git a/ui-ngx/src/app/core/utils.ts b/ui-ngx/src/app/core/utils.ts index 490f157ac3..ecde160b13 100644 --- a/ui-ngx/src/app/core/utils.ts +++ b/ui-ngx/src/app/core/utils.ts @@ -18,6 +18,7 @@ import _ from 'lodash'; import { Observable, Subject, fromEvent, of } from 'rxjs'; import { finalize, share, map } from 'rxjs/operators'; import base64js from 'base64-js'; +import { Datasource } from '@app/shared/models/widget.models'; export function onParentScrollOrWindowResize(el: Node): Observable { const scrollSubject = new Subject(); @@ -435,6 +436,34 @@ export function imageLoader(imageUrl: string): Observable { return imageLoad$; } +export function createLabelFromDatasource(datasource: Datasource, pattern: string) { + const varsRegex = /\$\{([^}]*)\}/g; + let label = pattern; + if (!datasource) { + return label; + } + let match = varsRegex.exec(pattern); + while (match !== null) { + const variable = match[0]; + const variableName = match[1]; + if (variableName === 'dsName') { + label = label.split(variable).join(datasource.name); + } else if (variableName === 'entityName') { + label = label.split(variable).join(datasource.entityName); + } else if (variableName === 'deviceName') { + label = label.split(variable).join(datasource.entityName); + } else if (variableName === 'entityLabel') { + label = label.split(variable).join(datasource.entityLabel || datasource.entityName); + } else if (variableName === 'aliasName') { + label = label.split(variable).join(datasource.aliasName); + } else if (variableName === 'entityDescription') { + label = label.split(variable).join(datasource.entityDescription); + } + match = varsRegex.exec(pattern); + } + return label; +} + const imageAspectMap = {}; export function aspectCache(imageUrl: string): Observable { @@ -452,7 +481,6 @@ export function aspectCache(imageUrl: string): Observable { } } - export function parseArray(input: any[]): any[] { return _(input).groupBy(el => el?.datasource?.entityName) .values().value().map((entityArray, dsIndex) => @@ -523,15 +551,18 @@ export function parseFunction(source: any, params: string[] = ['def']): Function return res; } -export function parseTemplate(template: string, data: object, translateFn?: (key: string) => string) { +export function parseTemplate(template: string, data: { $datasource?: Datasource, [key: string]: any }, + translateFn?: (key: string) => string) { let res = ''; try { if (template.match(//g, 'a>').replace(/name=(\'|")(.*?)(\'|")/g, `class='tb-custom-action' id='$2'`); + template = template.replace(//g, 'a>') + .replace(/name=(\'|")(.*?)(\'|")/g, `class='tb-custom-action' id='$2'`); } if (translateFn) { template = translateFn(template); } + template = createLabelFromDatasource(data.$datasource, template); const formatted = template.match(/\$\{([^}]*)\:\d*\}/g); if (formatted) formatted.forEach(value => { diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/alarms-table-widget.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/alarms-table-widget.component.ts index ee8f72048b..1eb7b39ca9 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/alarms-table-widget.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/alarms-table-widget.component.ts @@ -33,7 +33,7 @@ import { Datasource, WidgetActionDescriptor, WidgetConfig } from '@shared/models import { IWidgetSubscription } from '@core/api/widget-api.models'; import { UtilsService } from '@core/services/utils.service'; import { TranslateService } from '@ngx-translate/core'; -import { deepClone, isDefined, isNumber } from '@core/utils'; +import { deepClone, isDefined, isNumber, createLabelFromDatasource } from '@core/utils'; import cssjs from '@core/css/css'; import { PageLink } from '@shared/models/page/page-link'; import { Direction, SortOrder, sortOrderFromString } from '@shared/models/page/sort-order'; @@ -282,7 +282,7 @@ export class AlarmsTableWidgetComponent extends PageComponent implements OnInit, alarmsTitle = this.translate.instant('alarm.alarms'); } - this.ctx.widgetTitle = this.utils.createLabelFromDatasource(this.alarmSource, alarmsTitle); + this.ctx.widgetTitle = createLabelFromDatasource(this.alarmSource, alarmsTitle); this.enableSelection = isDefined(this.settings.enableSelection) ? this.settings.enableSelection : true; if (!this.allowAcknowledgment && !this.allowClear) { diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/entities-table-widget.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/entities-table-widget.component.ts index bbdb145835..54f8344890 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/entities-table-widget.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/entities-table-widget.component.ts @@ -39,7 +39,7 @@ import { import { IWidgetSubscription } from '@core/api/widget-api.models'; import { UtilsService } from '@core/services/utils.service'; import { TranslateService } from '@ngx-translate/core'; -import { deepClone, isDefined, isNumber } from '@core/utils'; +import { deepClone, isDefined, isNumber, createLabelFromDatasource } from '@core/utils'; import cssjs from '@core/css/css'; import { PageLink } from '@shared/models/page/page-link'; import { Direction, SortOrder, sortOrderFromString } from '@shared/models/page/sort-order'; @@ -210,7 +210,7 @@ export class EntitiesTableWidgetComponent extends PageComponent implements OnIni } const datasource = this.subscription.datasources[0]; - this.ctx.widgetTitle = this.utils.createLabelFromDatasource(datasource, entitiesTitle); + this.ctx.widgetTitle = createLabelFromDatasource(datasource, entitiesTitle); this.searchAction.show = isDefined(this.settings.enableSearch) ? this.settings.enableSearch : true; this.displayPagination = isDefined(this.settings.displayPagination) ? this.settings.displayPagination : true; diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/maps/leaflet-map.ts b/ui-ngx/src/app/modules/home/components/widget/lib/maps/leaflet-map.ts index 48909639ef..d98b564c2f 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/maps/leaflet-map.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/maps/leaflet-map.ts @@ -26,6 +26,7 @@ import { filter } from 'rxjs/operators'; import { Polyline } from './polyline'; import { Polygon } from './polygon'; import { DatasourceData } from '@app/shared/models/widget.models'; +import { safeExecute } from '@app/core/utils'; export default abstract class LeafletMap { @@ -87,12 +88,14 @@ export default abstract class LeafletMap { if (this.options.draggableMarker) { let mousePositionOnMap: L.LatLng; let addMarker: L.Control; - this.map.on('mouseup', (e: L.LeafletMouseEvent) => { + this.map.on('mousemove', (e: L.LeafletMouseEvent) => { mousePositionOnMap = e.latlng; }); const dragListener = (e: L.DragEndEvent) => { if (e.type === 'dragend' && mousePositionOnMap) { - const newMarker = L.marker(mousePositionOnMap).addTo(this.map); + const icon = new L.Icon.Default(); + icon.options.shadowSize = [0, 0]; + const newMarker = L.marker(mousePositionOnMap, { icon }).addTo(this.map); const datasourcesList = document.createElement('div'); const customLatLng = this.convertToCustomFormat(mousePositionOnMap); this.datasources.forEach(ds => { @@ -195,15 +198,18 @@ export default abstract class LeafletMap { fitBounds(bounds: LatLngBounds, useDefaultZoom = false, padding?: LatLngTuple) { if (bounds.isValid()) { - if ((!this.options.fitMapBounds || useDefaultZoom) && this.options.defaultZoomLevel) { + if ((!this.options.fitMapBounds || this.options.useDefaultCenterPosition) && this.options.defaultZoomLevel) { this.map.setZoom(this.options.defaultZoomLevel, { animate: false }); - this.map.panTo(bounds.getCenter(), { animate: false }); + this.map.panTo(this.options.defaultCenterPosition, { animate: false }); } else { this.map.once('zoomend', () => { if (!this.options.defaultZoomLevel && this.map.getZoom() > this.options.minZoomLevel) { this.map.setZoom(this.options.minZoomLevel, { animate: false }); } }); + if (this.options.useDefaultCenterPosition) { + bounds = bounds.extend(this.options.defaultCenterPosition); + } this.map.fitBounds(bounds, { padding: padding || [50, 50], animate: false }); } this.bounds = bounds; @@ -231,8 +237,16 @@ export default abstract class LeafletMap { updateMarkers(markersData) { markersData.filter(mdata => !!this.convertPosition(mdata)).forEach(data => { if (data.rotationAngle || data.rotationAngle === 0) { + const currentImage = this.options.useMarkerImageFunction ? + safeExecute(this.options.markerImageFunction, + [data, this.options.markerImages, markersData, data.dsIndex]) : this.options.currentImage; + const style = currentImage ? 'background-image: url(' + currentImage.url + ');' : ''; this.options.icon = L.divIcon({ - html: `
` + html: `
` }) } else { @@ -335,31 +349,28 @@ export default abstract class LeafletMap { data.data = JSON.parse(data.data[0][1]) as LatLngTuple[]; } if (this.polygons.get(data.datasource.entityName)) { - this.updatePolygon(data.datasource.entityName, data.data, polyData, this.options); + this.updatePolygon(data, polyData, this.options); } else { - this.createPolygon(data.datasource.entityName, data.data, polyData, this.options); + this.createPolygon(data, polyData, this.options); } } }); } - createPolygon(key: string, data: LatLngTuple[], dataSources: DatasourceData[], settings: PolygonSettings) { + createPolygon(polyData: DatasourceData, dataSources: DatasourceData[], settings: PolygonSettings) { this.ready$.subscribe(() => { - const polygon = new Polygon(this.map, data, dataSources, settings); + const polygon = new Polygon(this.map, polyData, dataSources, settings); const bounds = this.bounds.extend(polygon.leafletPoly.getBounds()); - if (bounds.isValid()) { - this.map.fitBounds(bounds); - this.bounds = bounds; - } - this.polygons.set(key, polygon); + this.fitBounds(bounds); + this.polygons.set(polyData.datasource.entityName, polygon); }); } - updatePolygon(key: string, data: LatLngTuple[], dataSources: DatasourceData[], settings: PolygonSettings) { + updatePolygon(polyData: DatasourceData, dataSources: DatasourceData[], settings: PolygonSettings) { this.ready$.subscribe(() => { - const poly = this.polygons.get(key); - poly.updatePolygon(data, dataSources, settings); + const poly = this.polygons.get(polyData.datasource.entityName); + poly.updatePolygon(polyData.data, dataSources, settings); this.fitBounds(poly.leafletPoly.getBounds()); }); } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/maps/map-models.ts b/ui-ngx/src/app/modules/home/components/widget/lib/maps/map-models.ts index b50ff189fb..eee38e54ed 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/maps/map-models.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/maps/map-models.ts @@ -111,9 +111,13 @@ export type PolygonSettings = { polygonStrokeWeight: number; polygonStrokeColor: string; polygonColor: string; + showPolygonTooltip: boolean; autocloseTooltip: boolean; + tooltipFunction: GenericFunction; showTooltipAction: string; tooltipAction: object; + tooltipPattern: string; + useTooltipFunction: boolean; polygonClick: { [name: string]: actionsHandler }; polygonColorFunction?: GenericFunction; } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/maps/polygon.ts b/ui-ngx/src/app/modules/home/components/widget/lib/maps/polygon.ts index ca601e3e15..e9e11acf48 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/maps/polygon.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/maps/polygon.ts @@ -16,8 +16,9 @@ import L, { LatLngExpression, LatLngTuple } from 'leaflet'; import { createTooltip } from './maps-utils'; -import { PolygonSettings } from './map-models'; +import { PolygonSettings, FormattedData } from './map-models'; import { DatasourceData } from '@app/shared/models/widget.models'; +import { safeExecute, parseWithTranslation } from '@app/core/utils'; export class Polygon { @@ -26,8 +27,8 @@ export class Polygon { data; dataSources; - constructor(public map, coordinates, dataSources, settings: PolygonSettings, onClickListener?) { - this.leafletPoly = L.polygon(coordinates, { + constructor(public map, polyData: DatasourceData, dataSources, private settings: PolygonSettings, onClickListener?) { + this.leafletPoly = L.polygon(polyData.data, { fill: true, fillColor: settings.polygonColor, color: settings.polygonStrokeColor, @@ -35,19 +36,29 @@ export class Polygon { fillOpacity: settings.polygonOpacity, opacity: settings.polygonStrokeOpacity }).addTo(this.map); - - if (settings.showTooltip) { + this.dataSources = dataSources; + this.data = polyData; + if (settings.showPolygonTooltip) { this.tooltip = createTooltip(this.leafletPoly, settings); + this.updateTooltip(polyData); } if (onClickListener) { this.leafletPoly.on('click', onClickListener); } } + updateTooltip(data: DatasourceData) { + const pattern = this.settings.useTooltipFunction ? + safeExecute(this.settings.tooltipFunction, [this.data, this.dataSources, this.data.dsIndex]) : this.settings.tooltipPattern; + this.tooltip.setContent(parseWithTranslation.parseTemplate(pattern, data, true)); + } + updatePolygon(data: LatLngTuple[], dataSources: DatasourceData[], settings: PolygonSettings) { this.data = data; this.dataSources = dataSources; this.leafletPoly.setLatLngs(data); + if (settings.showPolygonTooltip) + this.updateTooltip(this.data); this.updatePolygonColor(settings); } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/maps/schemes.ts b/ui-ngx/src/app/modules/home/components/widget/lib/maps/schemes.ts index 51740ae2ca..5e55af55f3 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/maps/schemes.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/maps/schemes.ts @@ -477,6 +477,11 @@ export const mapPolygonSchema = type: 'number', default: 1 }, + showPolygonTooltip: { + title: 'Show polygon tooltip', + type: 'boolean', + default: false + }, usePolygonColorFunction: { title: 'Use polygon color function', type: 'boolean', @@ -501,7 +506,7 @@ export const mapPolygonSchema = key: 'polygonStrokeColor', type: 'color' }, - 'polygonStrokeOpacity', 'polygonStrokeWeight', 'usePolygonColorFunction', + 'polygonStrokeOpacity', 'polygonStrokeWeight', 'usePolygonColorFunction', 'showPolygonTooltip', { key: 'polygonColorFunction', type: 'javascript' @@ -1137,7 +1142,7 @@ export const tripAnimationSchema = { rotationAngle: { title: 'Set additional rotation angle for marker (deg)', type: 'number', - default: 180 + default: 0 }, useMarkerImageFunction: { title: 'Use marker image function', diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/multiple-input-widget.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/multiple-input-widget.component.ts index 01adeacdce..890a91e76f 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/multiple-input-widget.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/multiple-input-widget.component.ts @@ -24,7 +24,7 @@ import { UtilsService } from '@core/services/utils.service'; import { TranslateService } from '@ngx-translate/core'; import { DataKey, Datasource, DatasourceData, DatasourceType, WidgetConfig } from '@shared/models/widget.models'; import { IWidgetSubscription } from '@core/api/widget-api.models'; -import { isDefined, isEqual, isUndefined } from '@core/utils'; +import { isDefined, isEqual, isUndefined, createLabelFromDatasource } from '@core/utils'; import { EntityType } from '@shared/models/entity-type.models'; import * as _moment from 'moment'; import { FormBuilder, FormGroup, ValidatorFn, Validators } from '@angular/forms'; @@ -331,7 +331,7 @@ export class MultipleInputWidgetComponent extends PageComponent implements OnIni } public getGroupTitle(datasource: Datasource): string { - return this.utils.createLabelFromDatasource(datasource, this.settings.groupTitle); + return createLabelFromDatasource(datasource, this.settings.groupTitle); } public visibleKeys(source: MultipleInputWidgetSource): MultipleInputWidgetDataKey[] {