diff --git a/ui-ngx/src/app/core/api/alarm-data-subscription.ts b/ui-ngx/src/app/core/api/alarm-data-subscription.ts index 3e2570896d..4d3524324e 100644 --- a/ui-ngx/src/app/core/api/alarm-data-subscription.ts +++ b/ui-ngx/src/app/core/api/alarm-data-subscription.ts @@ -31,7 +31,6 @@ import { } from '@shared/models/query/query.models'; import { SubscriptionTimewindow } from '@shared/models/time/time.models'; import { AlarmDataListener } from '@core/api/alarm-data.service'; -import { UtilsService } from '@core/services/utils.service'; import { PageData } from '@shared/models/page/page-data'; import { deepClone, isDefined, isDefinedAndNotNull, isObject } from '@core/utils'; import { simulatedAlarm } from '@shared/models/alarm.models'; @@ -68,8 +67,7 @@ export class AlarmDataSubscription { constructor(public alarmDataSubscriptionOptions: AlarmDataSubscriptionOptions, private listener: AlarmDataListener, - private telemetryService: TelemetryService, - private utils: UtilsService) { + private telemetryService: TelemetryService) { } public unsubscribe() { @@ -166,7 +164,7 @@ export class AlarmDataSubscription { private onPageData(pageData: PageData, allowedEntities: number, totalEntities: number) { this.pageData = pageData; this.resetData(); - this.listener.alarmsLoaded(pageData, this.alarmDataSubscriptionOptions.pageLink, allowedEntities, totalEntities); + this.listener.alarmsLoaded(pageData, allowedEntities, totalEntities); } private onDataUpdate(update: Array) { diff --git a/ui-ngx/src/app/core/api/alarm-data.service.ts b/ui-ngx/src/app/core/api/alarm-data.service.ts index 87d3ee02ca..23488c99c5 100644 --- a/ui-ngx/src/app/core/api/alarm-data.service.ts +++ b/ui-ngx/src/app/core/api/alarm-data.service.ts @@ -20,7 +20,6 @@ import { PageData } from '@shared/models/page/page-data'; import { AlarmData, AlarmDataPageLink, KeyFilter } from '@shared/models/query/query.models'; import { Injectable } from '@angular/core'; import { TelemetryWebsocketService } from '@core/ws/telemetry-websocket.service'; -import { UtilsService } from '@core/services/utils.service'; import { AlarmDataSubscription, AlarmDataSubscriptionOptions, @@ -31,7 +30,7 @@ import { deepClone } from '@core/utils'; export interface AlarmDataListener { subscriptionTimewindow?: SubscriptionTimewindow; alarmSource: Datasource; - alarmsLoaded: (pageData: PageData, pageLink: AlarmDataPageLink, allowedEntities: number, totalEntities: number) => void; + alarmsLoaded: (pageData: PageData, allowedEntities: number, totalEntities: number) => void; alarmsUpdated: (update: Array, pageData: PageData) => void; subscription?: AlarmDataSubscription; } @@ -41,8 +40,7 @@ export interface AlarmDataListener { }) export class AlarmDataService { - constructor(private telemetryService: TelemetryWebsocketService, - private utils: UtilsService) {} + constructor(private telemetryService: TelemetryWebsocketService) {} public subscribeForAlarms(listener: AlarmDataListener, @@ -88,7 +86,7 @@ export class AlarmDataService { alarmDataSubscriptionOptions.additionalKeyFilters = additionalKeyFilters; } return new AlarmDataSubscription(alarmDataSubscriptionOptions, - listener, this.telemetryService, this.utils); + listener, this.telemetryService); } } diff --git a/ui-ngx/src/app/core/utils.ts b/ui-ngx/src/app/core/utils.ts index dcfb90fbbc..e4823664bb 100644 --- a/ui-ngx/src/app/core/utils.ts +++ b/ui-ngx/src/app/core/utils.ts @@ -77,6 +77,10 @@ export function isUndefined(value: any): boolean { return typeof value === 'undefined'; } +export function isUndefinedOrNull(value: any): boolean { + return typeof value === 'undefined' || value === null; +} + export function isDefined(value: any): boolean { return typeof value !== 'undefined'; } @@ -452,7 +456,7 @@ export function insertVariable(pattern: string, name: string, value: any): strin const variable = match[0]; const variableName = match[1]; if (variableName === name) { - result = result.split(variable).join(value); + result = result.replace(variable, value); } match = varsRegex.exec(pattern); } @@ -469,17 +473,17 @@ export function createLabelFromDatasource(datasource: Datasource, pattern: strin const variable = match[0]; const variableName = match[1]; if (variableName === 'dsName') { - label = label.split(variable).join(datasource.name); + label = label.replace(variable, datasource.name); } else if (variableName === 'entityName') { - label = label.split(variable).join(datasource.entityName); + label = label.replace(variable, datasource.entityName); } else if (variableName === 'deviceName') { - label = label.split(variable).join(datasource.entityName); + label = label.replace(variable, datasource.entityName); } else if (variableName === 'entityLabel') { - label = label.split(variable).join(datasource.entityLabel || datasource.entityName); + label = label.replace(variable, datasource.entityLabel || datasource.entityName); } else if (variableName === 'aliasName') { - label = label.split(variable).join(datasource.aliasName); + label = label.replace(variable, datasource.aliasName); } else if (variableName === 'entityDescription') { - label = label.split(variable).join(datasource.entityDescription); + label = label.replace(variable, datasource.entityDescription); } match = varsRegex.exec(pattern); } diff --git a/ui-ngx/src/app/modules/home/components/widget/dynamic-widget.component.ts b/ui-ngx/src/app/modules/home/components/widget/dynamic-widget.component.ts index 3fdae9105d..e63adcb744 100644 --- a/ui-ngx/src/app/modules/home/components/widget/dynamic-widget.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/dynamic-widget.component.ts @@ -40,6 +40,7 @@ import { DialogService } from '@core/services/dialog.service'; import { CustomDialogService } from '@home/components/widget/dialog/custom-dialog.service'; import { DatePipe } from '@angular/common'; import { TranslateService } from '@ngx-translate/core'; +import { DomSanitizer } from '@angular/platform-browser'; export class DynamicWidgetComponent extends PageComponent implements IDynamicWidgetComponent, OnInit, OnDestroy { @@ -74,6 +75,7 @@ export class DynamicWidgetComponent extends PageComponent implements IDynamicWid this.ctx.date = $injector.get(DatePipe); this.ctx.translate = $injector.get(TranslateService); this.ctx.http = $injector.get(HttpClient); + this.ctx.sanitizer = $injector.get(DomSanitizer); this.ctx.$scope = this; if (this.ctx.defaultSubscription) { diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/alarms-table-widget.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/alarms-table-widget.component.html index 52b497494a..7e9a67c485 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/alarms-table-widget.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/alarms-table-widget.component.html @@ -121,9 +121,10 @@ - diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/alarms-table-widget.component.scss b/ui-ngx/src/app/modules/home/components/widget/lib/alarms-table-widget.component.scss index 7606ba5d75..7fe357b7c2 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/alarms-table-widget.component.scss +++ b/ui-ngx/src/app/modules/home/components/widget/lib/alarms-table-widget.component.scss @@ -16,4 +16,23 @@ :host { width: 100%; height: 100%; + .tb-table-widget { + .table-container { + position: relative; + } + .mat-table { + .mat-row { + &.invisible { + visibility: hidden; + } + } + } + span.no-data-found { + position: absolute; + top: 60px; + bottom: 0; + left: 0; + right: 0; + } + } } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/entities-table-widget.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/entities-table-widget.component.html index 8574076acd..a2c70a3a89 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/entities-table-widget.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/entities-table-widget.component.html @@ -82,7 +82,8 @@ - diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/entities-table-widget.component.scss b/ui-ngx/src/app/modules/home/components/widget/lib/entities-table-widget.component.scss index 7606ba5d75..7fe357b7c2 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/entities-table-widget.component.scss +++ b/ui-ngx/src/app/modules/home/components/widget/lib/entities-table-widget.component.scss @@ -16,4 +16,23 @@ :host { width: 100%; height: 100%; + .tb-table-widget { + .table-container { + position: relative; + } + .mat-table { + .mat-row { + &.invisible { + visibility: hidden; + } + } + } + span.no-data-found { + position: absolute; + top: 60px; + bottom: 0; + left: 0; + right: 0; + } + } } 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 49fdd19677..33c36e3ce8 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 @@ -16,6 +16,7 @@ import L, { FeatureGroup, + Icon, LatLngBounds, LatLngTuple, markerClusterGroup, @@ -32,6 +33,7 @@ import { MarkerSettings, PolygonSettings, PolylineSettings, + ReplaceInfo, UnitedMapSettings } from './map-models'; import { Marker } from './markers'; @@ -39,7 +41,7 @@ import { BehaviorSubject, Observable, of } from 'rxjs'; import { filter } from 'rxjs/operators'; import { Polyline } from './polyline'; import { Polygon } from './polygon'; -import { createTooltip, parseArray, safeExecute } from '@home/components/widget/lib/maps/maps-utils'; +import { createLoadingDiv, createTooltip, parseArray, safeExecute } from '@home/components/widget/lib/maps/maps-utils'; import { WidgetContext } from '@home/models/widget-component.models'; import { DatasourceData } from '@shared/models/widget.models'; import { deepClone, isDefinedAndNotNull } from '@core/utils'; @@ -59,6 +61,13 @@ export default abstract class LeafletMap { points: FeatureGroup; markersData: FormattedData[] = []; polygonsData: FormattedData[] = []; + defaultMarkerIconInfo: { size: number[], icon: Icon }; + loadingDiv: JQuery; + loading = false; + replaceInfoLabelMarker: Array = []; + markerLabelText: string; + replaceInfoTooltipMarker: Array = []; + markerTooltipText: string; protected constructor(public ctx: WidgetContext, public $container: HTMLElement, @@ -168,6 +177,24 @@ export default abstract class LeafletMap { } } + public setLoading(loading: boolean) { + if (this.loading !== loading) { + this.loading = loading; + this.ready$.subscribe(() => { + if (this.loading) { + if (!this.loadingDiv) { + this.loadingDiv = createLoadingDiv(this.ctx.translate.instant('common.loading')); + } + this.$container.append(this.loadingDiv[0]); + } else { + if (this.loadingDiv) { + this.loadingDiv.remove(); + } + } + }); + } + } + public setMap(map: L.Map) { this.map = map; if (this.options.useDefaultCenterPosition) { @@ -308,7 +335,11 @@ export default abstract class LeafletMap { updateMarkers(markersData: FormattedData[], updateBounds = true, callback?) { const rawMarkers = markersData.filter(mdata => !!this.convertPosition(mdata)); this.ready$.subscribe(() => { - const keys: string[] = []; + const toDelete = new Set(Array.from(this.markers.keys())); + const createdMarkers: Marker[] = []; + const updatedMarkers: Marker[] = []; + const deletedMarkers: Marker[] = []; + let m: Marker; rawMarkers.forEach(data => { if (data.rotationAngle || data.rotationAngle === 0) { const currentImage = this.options.useMarkerImageFunction ? @@ -325,22 +356,36 @@ export default abstract class LeafletMap { this.options.icon = null; } if (this.markers.get(data.entityName)) { - this.updateMarker(data.entityName, data, markersData, this.options) + m = this.updateMarker(data.entityName, data, markersData, this.options); + if (m) { + updatedMarkers.push(m); + } } else { - this.createMarker(data.entityName, data, markersData, this.options as MarkerSettings, updateBounds, callback); - } - keys.push(data.entityName); - }); - const toDelete: string[] = []; - this.markers.forEach((v, mKey) => { - if (!keys.includes(mKey)) { - toDelete.push(mKey); + m = this.createMarker(data.entityName, data, markersData, this.options as MarkerSettings, updateBounds, callback); + if (m) { + createdMarkers.push(m); + } } + toDelete.delete(data.entityName); }); toDelete.forEach((key) => { - this.deleteMarker(key); + m = this.deleteMarker(key); + if (m) { + deletedMarkers.push(m); + } }); this.markersData = markersData; + if ((this.options as MarkerSettings).useClusterMarkers) { + if (createdMarkers.length) { + this.markersCluster.addLayers(createdMarkers.map(marker => marker.leafletMarker)); + } + if (updatedMarkers.length) { + this.markersCluster.refreshClusters(updatedMarkers.map(marker => marker.leafletMarker)) + } + if (deletedMarkers.length) { + this.markersCluster.removeLayers(deletedMarkers.map(marker => marker.leafletMarker)); + } + } }); } @@ -350,22 +395,20 @@ export default abstract class LeafletMap { } private createMarker(key: string, data: FormattedData, dataSources: FormattedData[], settings: MarkerSettings, - updateBounds = true, callback?) { - const newMarker = new Marker(this.convertPosition(data), settings, data, dataSources, this.dragMarker); + updateBounds = true, callback?): Marker { + const newMarker = new Marker(this, this.convertPosition(data), settings, data, dataSources, this.dragMarker); if (callback) newMarker.leafletMarker.on('click', () => { callback(data, true) }); if (this.bounds && updateBounds) this.fitBounds(this.bounds.extend(newMarker.leafletMarker.getLatLng())); this.markers.set(key, newMarker); - if (this.options.useClusterMarkers) { - this.markersCluster.addLayer(newMarker.leafletMarker); - } - else { - this.map.addLayer(newMarker.leafletMarker); + if (!this.options.useClusterMarkers) { + this.map.addLayer(newMarker.leafletMarker); } + return newMarker; } - private updateMarker(key: string, data: FormattedData, dataSources: FormattedData[], settings: MarkerSettings) { + private updateMarker(key: string, data: FormattedData, dataSources: FormattedData[], settings: MarkerSettings): Marker { const marker: Marker = this.markers.get(key); const location = this.convertPosition(data) if (!location.equals(marker.location)) { @@ -374,24 +417,21 @@ export default abstract class LeafletMap { if (settings.showTooltip) { marker.updateMarkerTooltip(data); } - if (settings.useClusterMarkers) { - this.markersCluster.refreshClusters() - } marker.setDataSources(data, dataSources); marker.updateMarkerIcon(settings); + return marker; } - deleteMarker(key: string) { - let marker = this.markers.get(key)?.leafletMarker; - if (marker) { - if (this.options.useClusterMarkers) { - this.markersCluster.removeLayer(marker); - } else { - this.map.removeLayer(marker); - } - this.markers.delete(key); - marker = null; - } + deleteMarker(key: string): Marker { + const marker = this.markers.get(key); + const leafletMarker = marker?.leafletMarker; + if (leafletMarker) { + if (!this.options.useClusterMarkers) { + this.map.removeLayer(leafletMarker); + } + this.markers.delete(key); + } + return marker; } updatePoints(pointsData: FormattedData[], getTooltip: (point: FormattedData, setTooltip?: boolean) => string) { 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 32cf92eeae..0a41516b6e 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 @@ -121,6 +121,12 @@ export interface FormattedData { [key: string]: any } +export interface ReplaceInfo { + variable: string; + valDec?: number; + dataKeyName: string +} + export type PolygonSettings = { showPolygon: boolean; polygonKeyName: string; diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/maps/map-widget2.ts b/ui-ngx/src/app/modules/home/components/widget/lib/maps/map-widget2.ts index fd06598fb3..7acc6e92e5 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/maps/map-widget2.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/maps/map-widget2.ts @@ -85,6 +85,7 @@ export class MapWidgetController implements MapWidgetInterface { textSearch: null, dynamic: true }; + this.map.setLoading(true); this.ctx.defaultSubscription.subscribeAllForPaginatedData(this.pageLink, null); } @@ -279,6 +280,7 @@ export class MapWidgetController implements MapWidgetInterface { if (this.settings.draggableMarker) { this.map.setDataSources(formattedData); } + this.map.setLoading(false); } resize() { diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/maps/maps-utils.ts b/ui-ngx/src/app/modules/home/components/widget/lib/maps/maps-utils.ts index b67925038c..a94144c061 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/maps/maps-utils.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/maps/maps-utils.ts @@ -15,13 +15,12 @@ /// import L from 'leaflet'; -import { FormattedData, MarkerSettings, PolygonSettings, PolylineSettings } from './map-models'; +import { FormattedData, MarkerSettings, PolygonSettings, PolylineSettings, ReplaceInfo } from './map-models'; import { Datasource, DatasourceData } from '@app/shared/models/widget.models'; import _ from 'lodash'; import { Observable, Observer, of } from 'rxjs'; import { map } from 'rxjs/operators'; -import { createLabelFromDatasource, hashCode, isNumber, isUndefined, padValue } from '@core/utils'; -import { Form } from '@angular/forms'; +import { createLabelFromDatasource, hashCode, isDefinedAndNotNull, isNumber, isUndefined, padValue } from '@core/utils'; export function createTooltip(target: L.Layer, settings: MarkerSettings | PolylineSettings | PolygonSettings, @@ -185,7 +184,7 @@ function parseTemplate(template: string, data: { $datasource?: Datasource, [key: } else { textValue = value; } - template = template.split(variable).join(textValue); + template = template.replace(variable, textValue); match = /\${([^}]*)}/g.exec(template); } @@ -198,7 +197,7 @@ function parseTemplate(template: string, data: { $datasource?: Datasource, [key: while (match !== null) { [actionTags, actionName, actionText] = match; action = createLinkElement(actionName, actionText); - template = template.split(actionTags).join(action); + template = template.replace(actionTags, action); match = linkActionRegex.exec(template); } @@ -206,18 +205,107 @@ function parseTemplate(template: string, data: { $datasource?: Datasource, [key: while (match !== null) { [actionTags, actionName, actionText] = match; action = createButtonElement(actionName, actionText); - template = template.split(actionTags).join(action); + template = template.replace(actionTags, action); match = buttonActionRegex.exec(template); } - const compiled = _.template(template); - res = compiled(data); + // const compiled = _.template(template); + // res = compiled(data); + res = template; } catch (ex) { console.log(ex, template) } return res; } +export function processPattern(template: string, data: { $datasource?: Datasource, [key: string]: any }): Array { + const replaceInfo = []; + try { + const reg = /\${([^}]*)}/g; + let match = reg.exec(template); + while (match !== null) { + const variableInfo: ReplaceInfo = { + dataKeyName: '', + valDec: 2, + variable: '' + }; + const variable = match[0]; + let label = match[1]; + let valDec = 2; + const splitValues = label.split(':'); + if (splitValues.length > 1) { + label = splitValues[0]; + valDec = parseFloat(splitValues[1]); + } + + variableInfo.variable = variable; + variableInfo.valDec = valDec; + + if (label.startsWith('#')) { + const keyIndexStr = label.substring(1); + const n = Math.floor(Number(keyIndexStr)); + if (String(n) === keyIndexStr && n >= 0) { + variableInfo.dataKeyName = data.$datasource.dataKeys[n].label; + } + } else { + variableInfo.dataKeyName = label; + } + replaceInfo.push(variableInfo); + + match = reg.exec(template); + } + } catch (ex) { + console.log(ex, template) + } + return replaceInfo; +} + +export function fillPattern(markerLabelText: string, replaceInfoLabelMarker: Array, data: FormattedData) { + let text = createLabelFromDatasource(data.$datasource, markerLabelText); + if (replaceInfoLabelMarker) { + for(const variableInfo of replaceInfoLabelMarker) { + let txtVal = ''; + if (variableInfo.dataKeyName && isDefinedAndNotNull(data[variableInfo.dataKeyName])) { + const varData = data[variableInfo.dataKeyName]; + if (isNumber(varData)) { + txtVal = padValue(varData, variableInfo.valDec); + } else { + txtVal = varData; + } + } + text = text.replace(variableInfo.variable, txtVal); + } + } + return text; +} + +function prepareProcessPattern(template: string, translateFn?: TranslateFunc): string { + if (translateFn) { + template = translateFn(template); + } + let actionTags: string; + let actionText: string; + let actionName: string; + let action: string; + + let match = linkActionRegex.exec(template); + while (match !== null) { + [actionTags, actionName, actionText] = match; + action = createLinkElement(actionName, actionText); + template = template.replace(actionTags, action); + match = linkActionRegex.exec(template); + } + + match = buttonActionRegex.exec(template); + while (match !== null) { + [actionTags, actionName, actionText] = match; + action = createButtonElement(actionName, actionText); + template = template.replace(actionTags, action); + match = buttonActionRegex.exec(template); + } + return template; +} + export const parseWithTranslation = { translateFn: null, @@ -232,6 +320,9 @@ export const parseWithTranslation = { parseTemplate(template: string, data: object, forceTranslate = false): string { return parseTemplate(forceTranslate ? this.translate(template) : template, data, this.translate.bind(this)); }, + prepareProcessPattern(template: string, forceTranslate = false): string { + return prepareProcessPattern(forceTranslate ? this.translate(template) : template, this.translate.bind(this)); + }, setTranslate(translateFn: TranslateFunc) { this.translateFn = translateFn; } @@ -321,3 +412,28 @@ export function calculateNewPointCoordinate(coordinate: number, imageSize: numbe } return pointCoordinate; } + +export function createLoadingDiv(loadingText: string): JQuery { + return $(` +
+ ${loadingText} +
+ `); +} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/maps/markers.ts b/ui-ngx/src/app/modules/home/components/widget/lib/maps/markers.ts index eec2e257ea..cd254ca406 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/maps/markers.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/maps/markers.ts @@ -16,9 +16,18 @@ import L, { LeafletMouseEvent } from 'leaflet'; import { FormattedData, MarkerSettings } from './map-models'; -import { aspectCache, bindPopupActions, createTooltip, parseWithTranslation, safeExecute } from './maps-utils'; +import { + aspectCache, + bindPopupActions, + createTooltip, + fillPattern, + parseWithTranslation, + processPattern, + safeExecute +} from './maps-utils'; import tinycolor from 'tinycolor2'; import { isDefined } from '@core/utils'; +import LeafletMap from './leaflet-map'; export class Marker { leafletMarker: L.Marker; @@ -29,7 +38,7 @@ export class Marker { data: FormattedData; dataSources: FormattedData[]; - constructor(location: L.LatLngExpression, public settings: MarkerSettings, + constructor(private map: LeafletMap, location: L.LatLngExpression, public settings: MarkerSettings, data?: FormattedData, dataSources?, onDragendListener?) { this.setDataSources(data, dataSources); this.leafletMarker = L.marker(location, { @@ -73,9 +82,13 @@ export class Marker { } updateMarkerTooltip(data: FormattedData) { + if(!this.map.markerTooltipText || this.settings.useTooltipFunction) { 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)); + safeExecute(this.settings.tooltipFunction, [this.data, this.dataSources, this.data.dsIndex]) : this.settings.tooltipPattern; + this.map.markerTooltipText = parseWithTranslation.prepareProcessPattern(pattern, true); + this.map.replaceInfoTooltipMarker = processPattern(this.map.markerTooltipText, data); + } + this.tooltip.setContent(fillPattern(this.map.markerTooltipText, this.map.replaceInfoTooltipMarker, data)); if (this.tooltip.isOpen() && this.tooltip.getElement()) { bindPopupActions(this.tooltip, this.settings, data.$datasource); } @@ -88,9 +101,13 @@ export class Marker { updateMarkerLabel(settings: MarkerSettings) { this.leafletMarker.unbindTooltip(); if (settings.showLabel) { - const pattern = settings.useLabelFunction ? + if(!this.map.markerLabelText || settings.useLabelFunction) { + const pattern = settings.useLabelFunction ? safeExecute(settings.labelFunction, [this.data, this.dataSources, this.data.dsIndex]) : settings.label; - settings.labelText = parseWithTranslation.parseTemplate(pattern, this.data, true); + this.map.markerLabelText = parseWithTranslation.prepareProcessPattern(pattern, true); + this.map.replaceInfoLabelMarker = processPattern(this.map.markerLabelText, this.data); + } + settings.labelText = fillPattern(this.map.markerLabelText, this.map.replaceInfoLabelMarker, this.data); this.leafletMarker.bindTooltip(`
${settings.labelText}
`, { className: 'tb-marker-label', permanent: true, direction: 'top', offset: this.tooltipOffset }); } @@ -158,24 +175,24 @@ export class Marker { } createDefaultMarkerIcon(color, onMarkerIconReady) { + if (!this.map.defaultMarkerIconInfo) { const icon = L.icon({ - iconUrl: 'https://chart.apis.google.com/chart?chst=d_map_pin_letter&chld=%E2%80%A2|' + color, - iconSize: [21, 34], - iconAnchor: [21 * this.markerOffset[0], 34 * this.markerOffset[1]], - popupAnchor: [0, -34], - shadowUrl: 'https://chart.apis.google.com/chart?chst=d_map_pin_shadow', - shadowSize: [40, 37], - shadowAnchor: [12, 35] + iconUrl: 'https://chart.apis.google.com/chart?chst=d_map_pin_letter&chld=%E2%80%A2|' + color, + iconSize: [21, 34], + iconAnchor: [21 * this.markerOffset[0], 34 * this.markerOffset[1]], + popupAnchor: [0, -34], + shadowUrl: 'https://chart.apis.google.com/chart?chst=d_map_pin_shadow', + shadowSize: [40, 37], + shadowAnchor: [12, 35] }); - const iconInfo = { - size: [21, 34], - icon + this.map.defaultMarkerIconInfo = { + size: [21, 34], + icon }; - onMarkerIconReady(iconInfo); + } + onMarkerIconReady(this.map.defaultMarkerIconInfo); } - - removeMarker() { /* this.map$.subscribe(map => this.leafletMarker.addTo(map))*/ diff --git a/ui-ngx/src/app/modules/home/models/widget-component.models.ts b/ui-ngx/src/app/modules/home/models/widget-component.models.ts index 96fae7bac3..289296b159 100644 --- a/ui-ngx/src/app/modules/home/models/widget-component.models.ts +++ b/ui-ngx/src/app/modules/home/models/widget-component.models.ts @@ -75,6 +75,7 @@ import { DatePipe } from '@angular/common'; import { TranslateService } from '@ngx-translate/core'; import { PageLink } from '@shared/models/page/page-link'; import { SortOrder } from '@shared/models/page/sort-order'; +import { DomSanitizer } from '@angular/platform-browser'; export interface IWidgetAction { name: string; @@ -155,6 +156,7 @@ export class WidgetContext { date: DatePipe; translate: TranslateService; http: HttpClient; + sanitizer: DomSanitizer; private changeDetectorValue: ChangeDetectorRef; diff --git a/ui-ngx/src/app/shared/models/ace/service-completion.models.ts b/ui-ngx/src/app/shared/models/ace/service-completion.models.ts index b94365a726..63bc4cd938 100644 --- a/ui-ngx/src/app/shared/models/ace/service-completion.models.ts +++ b/ui-ngx/src/app/shared/models/ace/service-completion.models.ts @@ -1382,5 +1382,11 @@ export const serviceCompletions: TbEditorCompletions = { 'See HttpClient for API reference.', meta: 'service', type: 'HttpClient' + }, + sanitizer: { + description: 'DomSanitizer Service
' + + 'See DomSanitizer for API reference.', + meta: 'service', + type: 'DomSanitizer' } } diff --git a/ui-ngx/src/app/shared/models/ace/widget-completion.models.ts b/ui-ngx/src/app/shared/models/ace/widget-completion.models.ts index 53a04e92a2..acdcc080ba 100644 --- a/ui-ngx/src/app/shared/models/ace/widget-completion.models.ts +++ b/ui-ngx/src/app/shared/models/ace/widget-completion.models.ts @@ -579,6 +579,23 @@ export const widgetContextCompletions: TbEditorCompletions = { } ] }, + pushAndOpenState: { + description: 'Navigate to new dashboard state and adding intermediate states.', + meta: 'function', + args: [ + { + name: 'id', + description: 'An array state object of the target dashboard state.', + type: 'Array StateObject', + }, + { + name: 'openRightLayout', + description: 'An optional boolean argument to force open right dashboard layout if present in mobile view mode.', + type: 'boolean', + optional: true + } + ] + }, updateState: { description: 'Updates current dashboard state.', meta: 'function',