From a0dcd7cf3058d7fe534987c062a78ab63f8afb47 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Tue, 3 Dec 2024 17:07:45 +0200 Subject: [PATCH] UI: Add JS modules support to map widgets JS functions. --- ui-ngx/src/app/core/utils.ts | 15 -- .../home/components/widget/lib/maps/circle.ts | 11 +- .../widget/lib/maps/common-maps-utils.ts | 5 +- .../components/widget/lib/maps/leaflet-map.ts | 10 +- .../components/widget/lib/maps/map-models.ts | 78 +++++----- .../components/widget/lib/maps/map-widget2.ts | 142 +++++++++++------- .../components/widget/lib/maps/maps-utils.ts | 12 +- .../components/widget/lib/maps/markers.ts | 16 +- .../components/widget/lib/maps/polygon.ts | 11 +- .../widget/lib/maps/providers/image-map.ts | 22 ++- .../map/circle-settings.component.html | 4 + .../marker-clustering-settings.component.html | 1 + .../map/markers-settings.component.html | 5 + .../map/polygon-settings.component.html | 4 + ...p-animation-common-settings.component.html | 1 + ...p-animation-marker-settings.component.html | 2 + ...rip-animation-path-settings.component.html | 1 + ...ip-animation-point-settings.component.html | 2 + .../trip-animation.component.ts | 123 ++++++++++----- 19 files changed, 279 insertions(+), 186 deletions(-) diff --git a/ui-ngx/src/app/core/utils.ts b/ui-ngx/src/app/core/utils.ts index b67f41d0cf..fed2de125f 100644 --- a/ui-ngx/src/app/core/utils.ts +++ b/ui-ngx/src/app/core/utils.ts @@ -711,21 +711,6 @@ export function safeExecuteTbFunction(func: CompiledT return res; } - -export function safeExecute(func: (...args: any[]) => any, params = []) { - let res = null; - if (func && typeof (func) === 'function') { - try { - res = func(...params); - } - catch (err) { - console.log('error in external function:', err); - res = null; - } - } - return res; -} - export function padValue(val: any, dec: number): string { let strVal; let n; diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/maps/circle.ts b/ui-ngx/src/app/modules/home/components/widget/lib/maps/circle.ts index 225dc66e06..680f3faca6 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/maps/circle.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/maps/circle.ts @@ -16,14 +16,11 @@ import L, { LeafletMouseEvent } from 'leaflet'; import { CircleData, WidgetCircleSettings } from '@home/components/widget/lib/maps/map-models'; -import { - functionValueCalculator, - parseWithTranslation -} from '@home/components/widget/lib/maps/common-maps-utils'; +import { functionValueCalculator, parseWithTranslation } from '@home/components/widget/lib/maps/common-maps-utils'; import LeafletMap from '@home/components/widget/lib/maps/leaflet-map'; import { createTooltip } from '@home/components/widget/lib/maps/maps-utils'; import { FormattedData } from '@shared/models/widget.models'; -import { fillDataPattern, processDataPattern, safeExecute } from '@core/utils'; +import { fillDataPattern, processDataPattern, safeExecuteTbFunction } from '@core/utils'; export class Circle { @@ -94,7 +91,7 @@ export class Circle { if (this.settings.showCircleLabel) { if (!this.map.circleLabelText || this.settings.useCircleLabelFunction) { const pattern = this.settings.useCircleLabelFunction ? - safeExecute(this.settings.parsedCircleLabelFunction, + safeExecuteTbFunction(this.settings.parsedCircleLabelFunction, [this.data, this.dataSources, this.data.dsIndex]) : this.settings.circleLabel; this.map.circleLabelText = parseWithTranslation.prepareProcessPattern(pattern, true); this.map.replaceInfoTooltipCircle = processDataPattern(this.map.circleLabelText, this.data); @@ -109,7 +106,7 @@ export class Circle { private updateTooltip() { const pattern = this.settings.useCircleTooltipFunction ? - safeExecute(this.settings.parsedCircleTooltipFunction, [this.data, this.dataSources, this.data.dsIndex]) : + safeExecuteTbFunction(this.settings.parsedCircleTooltipFunction, [this.data, this.dataSources, this.data.dsIndex]) : this.settings.circleTooltipPattern; this.tooltip.setContent(parseWithTranslation.parseTemplate(pattern, this.data, true)); } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/maps/common-maps-utils.ts b/ui-ngx/src/app/modules/home/components/widget/lib/maps/common-maps-utils.ts index 25f7b1ceec..cd974ee22c 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/maps/common-maps-utils.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/maps/common-maps-utils.ts @@ -29,6 +29,7 @@ import { map } from 'rxjs/operators'; import { FormattedData } from '@shared/models/widget.models'; import L from 'leaflet'; import { ImagePipe } from '@shared/pipe/image.pipe'; +import { CompiledTbFunction, GenericFunction } from '@shared/models/js-function.models'; export function getRatio(firsMoment: number, secondMoment: number, intermediateMoment: number): number { return (intermediateMoment - firsMoment) / (secondMoment - firsMoment); @@ -257,11 +258,11 @@ export const parseWithTranslation = { } }; -export function functionValueCalculator(useFunction: boolean, func: (...args: any[]) => any, params = [], defaultValue: T): T { +export function functionValueCalculator(useFunction: boolean, func: CompiledTbFunction, params = [], defaultValue: T): T { let res: T; if (useFunction && isDefined(func) && isFunction(func)) { try { - res = func(...params); + res = func.execute(...params); if (!isDefinedAndNotNull(res) || res === '') { res = defaultValue; } 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 29b2768e58..ee5af0bfb5 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 @@ -54,7 +54,7 @@ import { isNotEmptyStr, isString, mergeFormattedData, - safeExecute + safeExecuteTbFunction } from '@core/utils'; import { TranslateService } from '@ngx-translate/core'; import { @@ -63,9 +63,9 @@ import { } from '@home/components/widget/lib/maps/dialogs/select-entity-dialog.component'; import { MatDialog } from '@angular/material/dialog'; import { FormattedData, ReplaceInfo } from '@shared/models/widget.models'; -import ITooltipsterInstance = JQueryTooltipster.ITooltipsterInstance; import { ImagePipe } from '@shared/pipe/image.pipe'; import { take, tap } from 'rxjs/operators'; +import ITooltipsterInstance = JQueryTooltipster.ITooltipsterInstance; export default abstract class LeafletMap { @@ -149,7 +149,7 @@ export default abstract class LeafletMap { const childCount = cluster.getChildCount(); const formattedData = cluster.getAllChildMarkers().map(clusterMarker => clusterMarker.options.tbMarkerData); const markerColor = markerClusteringSettings.clusterMarkerFunction - ? safeExecute(markerClusteringSettings.parsedClusterMarkerFunction, + ? safeExecuteTbFunction(markerClusteringSettings.parsedClusterMarkerFunction, [formattedData, childCount]) : null; if (isDefinedAndNotNull(markerColor) && tinycolor(markerColor).isValid()) { @@ -899,7 +899,7 @@ export default abstract class LeafletMap { rawMarkers.forEach(data => { if (data.rotationAngle || data.rotationAngle === 0) { const currentImage: MarkerImageInfo = this.options.useMarkerImageFunction ? - safeExecute(this.options.parsedMarkerImageFunction, + safeExecuteTbFunction(this.options.parsedMarkerImageFunction, [data, this.options.markerImages, markersData, data.dsIndex]) : this.options.currentImage; const imageUrl$ = currentImage @@ -1042,7 +1042,7 @@ export default abstract class LeafletMap { if (!!this.extractPosition(pdata)) { const dsData = pointsData.map(ds => ds[tsIndex]); if (this.options.useColorPointFunction) { - pointColor = safeExecute(this.options.parsedColorPointFunction, [pdata, dsData, pdata.dsIndex]); + pointColor = safeExecuteTbFunction(this.options.parsedColorPointFunction, [pdata, dsData, pdata.dsIndex]); } const point = L.circleMarker(this.convertPosition(pdata, dsData), { color: pointColor, 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 af8f51ffdd..6705c7b853 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 @@ -18,6 +18,7 @@ import { Datasource, FormattedData } from '@app/shared/models/widget.models'; import tinycolor from 'tinycolor2'; import { BaseIconOptions, Icon } from 'leaflet'; import { Observable } from 'rxjs'; +import { CompiledTbFunction, TbFunction } from '@shared/models/js-function.models'; export const DEFAULT_MAP_PAGE_SIZE = 16384; export const DEFAULT_ZOOM_LEVEL = 8; @@ -273,7 +274,7 @@ export interface TripAnimationCommonSettings { } export interface WidgetTripAnimationCommonSettings extends TripAnimationCommonSettings { - parsedTooltipFunction: GenericFunction; + parsedTooltipFunction: CompiledTbFunction; } export const defaultTripAnimationCommonSettings: TripAnimationCommonSettings = { @@ -305,35 +306,35 @@ export const showTooltipActionTranslationMap = new Map; + parsedTooltipFunction: CompiledTbFunction; + parsedColorFunction: CompiledTbFunction; + parsedMarkerImageFunction: CompiledTbFunction; markerClick: { [name: string]: actionsHandler }; currentImage: MarkerImageInfo; tinyColor: tinycolor.Instance; @@ -382,8 +383,7 @@ export interface TripAnimationMarkerSettings { } export interface WidgetTripAnimationMarkerSettings extends TripAnimationMarkerSettings { - parsedLabelFunction: GenericFunction; - parsedMarkerImageFunction: MarkerImageFunction; + parsedLabelFunction: CompiledTbFunction; } export const defaultTripAnimationMarkersSettings: TripAnimationMarkerSettings = { @@ -406,29 +406,29 @@ export interface PolygonSettings { showPolygonLabel: boolean; usePolygonLabelFunction: boolean; polygonLabel?: string; - polygonLabelFunction?: string; + polygonLabelFunction?: TbFunction; showPolygonTooltip: boolean; showPolygonTooltipAction: ShowTooltipAction; autoClosePolygonTooltip: boolean; usePolygonTooltipFunction: boolean; polygonTooltipPattern?: string; - polygonTooltipFunction?: string; + polygonTooltipFunction?: TbFunction; polygonColor?: string; polygonOpacity?: number; usePolygonColorFunction: boolean; - polygonColorFunction?: string; + polygonColorFunction?: TbFunction; polygonStrokeColor?: string; polygonStrokeOpacity?: number; polygonStrokeWeight?: number; usePolygonStrokeColorFunction: boolean; - polygonStrokeColorFunction?: string; + polygonStrokeColorFunction?: TbFunction; } export interface WidgetPolygonSettings extends PolygonSettings, WidgetToolipSettings { - parsedPolygonLabelFunction: GenericFunction; - parsedPolygonTooltipFunction: GenericFunction; - parsedPolygonColorFunction: GenericFunction; - parsedPolygonStrokeColorFunction: GenericFunction; + parsedPolygonLabelFunction: CompiledTbFunction; + parsedPolygonTooltipFunction: CompiledTbFunction; + parsedPolygonColorFunction: CompiledTbFunction; + parsedPolygonStrokeColorFunction: CompiledTbFunction; polygonClick: { [name: string]: actionsHandler }; } @@ -464,29 +464,29 @@ export interface CircleSettings { showCircleLabel: boolean; useCircleLabelFunction: boolean; circleLabel?: string; - circleLabelFunction?: string; + circleLabelFunction?: TbFunction; showCircleTooltip: boolean; showCircleTooltipAction: ShowTooltipAction; autoCloseCircleTooltip: boolean; useCircleTooltipFunction: boolean; circleTooltipPattern?: string; - circleTooltipFunction?: string; + circleTooltipFunction?: TbFunction; circleFillColor?: string; circleFillColorOpacity?: number; useCircleFillColorFunction: boolean; - circleFillColorFunction?: string; + circleFillColorFunction?: TbFunction; circleStrokeColor?: string; circleStrokeOpacity?: number; circleStrokeWeight?: number; useCircleStrokeColorFunction: boolean; - circleStrokeColorFunction?: string; + circleStrokeColorFunction?: TbFunction; } export interface WidgetCircleSettings extends CircleSettings, WidgetToolipSettings { - parsedCircleLabelFunction: GenericFunction; - parsedCircleTooltipFunction: GenericFunction; - parsedCircleFillColorFunction: GenericFunction; - parsedCircleStrokeColorFunction: GenericFunction; + parsedCircleLabelFunction: CompiledTbFunction; + parsedCircleTooltipFunction: CompiledTbFunction; + parsedCircleFillColorFunction: CompiledTbFunction; + parsedCircleStrokeColorFunction: CompiledTbFunction; circleClick: { [name: string]: actionsHandler }; } @@ -530,13 +530,13 @@ export const polylineDecoratorSymbolTranslationMap = new Map; + parsedStrokeOpacityFunction: CompiledTbFunction; + parsedStrokeWeightFunction: CompiledTbFunction; } export const defaultRouteMapSettings: PolylineSettings = { @@ -578,7 +578,7 @@ export interface PointsSettings { showPoints?: boolean; pointColor?: string; useColorPointFunction?: false; - colorPointFunction?: string; + colorPointFunction?: TbFunction; pointSize?: number; usePointAsAnchor?: false; pointAsAnchorFunction?: string; @@ -586,8 +586,8 @@ export interface PointsSettings { } export interface WidgetPointsSettings extends PointsSettings { - parsedColorPointFunction: GenericFunction; - parsedPointAsAnchorFunction: GenericFunction; + parsedColorPointFunction: CompiledTbFunction; + parsedPointAsAnchorFunction: CompiledTbFunction; } export const defaultTripAnimationPointSettings: PointsSettings = { @@ -612,11 +612,11 @@ export interface MarkerClusteringSettings { chunkedLoading: boolean; removeOutsideVisibleBounds: boolean; useIconCreateFunction: boolean; - clusterMarkerFunction?: string; + clusterMarkerFunction?: TbFunction; } export interface WidgetMarkerClusteringSettings extends MarkerClusteringSettings { - parsedClusterMarkerFunction?: GenericFunction; + parsedClusterMarkerFunction?: CompiledTbFunction; } export const defaultMarkerClusteringSettings: MarkerClusteringSettings = { 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 58702653e5..8dea835e24 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 @@ -30,9 +30,9 @@ import { TranslateService } from '@ngx-translate/core'; import { UtilsService } from '@core/services/utils.service'; import { EntityDataPageLink } from '@shared/models/query/query.models'; import { providerClass } from '@home/components/widget/lib/maps/providers/public-api'; -import { isDefined, isDefinedAndNotNull, parseFunction } from '@core/utils'; +import { isDefined, isDefinedAndNotNull, parseFunction, parseTbFunction } from '@core/utils'; import L from 'leaflet'; -import { forkJoin, Observable, of } from 'rxjs'; +import { firstValueFrom, forkJoin, from, Observable, of } from 'rxjs'; import { AttributeService } from '@core/http/attribute.service'; import { EntityId } from '@shared/models/id/entity-id'; import { AttributeScope, DataKeyType, LatestTelemetry } from '@shared/models/telemetry/telemetry.models'; @@ -40,12 +40,18 @@ import { AttributeScope, DataKeyType, LatestTelemetry } from '@shared/models/tel // @dynamic export class MapWidgetController implements MapWidgetInterface { + private updatePending = false; + private latestUpdatePending = false; + private resizePending = false; + private destroyed = false; + constructor( public mapProvider: MapProviders, private drawRoutes: boolean, public ctx: WidgetContext, $element: HTMLElement, - isEdit?: boolean + isEdit = false, + mapLoaded?: (map: LeafletMap) => void ) { if (this.map) { this.map.map.remove(); @@ -56,37 +62,53 @@ export class MapWidgetController implements MapWidgetInterface { if (!$element) { $element = ctx.$container[0]; } - this.settings = this.initSettings(ctx.settings, isEdit); - this.settings.tooltipAction = this.getDescriptors('tooltipAction'); - this.settings.markerClick = this.getDescriptors('markerClick'); - this.settings.polygonClick = this.getDescriptors('polygonClick'); - this.settings.circleClick = this.getDescriptors('circleClick'); + from(this.initSettings(ctx.settings, isEdit)).subscribe(settings => { + if (!this.destroyed) { + this.settings = settings; + this.settings.tooltipAction = this.getDescriptors('tooltipAction'); + this.settings.markerClick = this.getDescriptors('markerClick'); + this.settings.polygonClick = this.getDescriptors('polygonClick'); + this.settings.circleClick = this.getDescriptors('circleClick'); - const MapClass = providerClass[this.provider]; - if (!MapClass) { - return; - } - parseWithTranslation.setTranslate(this.translate); - this.map = new MapClass(this.ctx, $element, this.settings); - (this.ctx as any).mapInstance = this.map; - this.map.saveMarkerLocation = this.setMarkerLocation.bind(this); - this.map.savePolygonLocation = this.savePolygonLocation.bind(this); - this.map.saveLocation = this.saveLocation.bind(this); - let pageSize = this.settings.mapPageSize; - if (isDefinedAndNotNull(this.ctx.widgetConfig.pageSize)) { - pageSize = Math.max(pageSize, this.ctx.widgetConfig.pageSize); - } - this.pageLink = { - page: 0, - pageSize, - textSearch: null, - dynamic: true - }; - this.map.setLoading(true); - this.ctx.defaultSubscription.paginatedDataSubscriptionUpdated.subscribe(() => { - this.map.resetState(); + const MapClass = providerClass[this.provider]; + if (!MapClass) { + return; + } + parseWithTranslation.setTranslate(this.translate); + this.map = new MapClass(this.ctx, $element, this.settings); + (this.ctx as any).mapInstance = this.map; + this.map.saveMarkerLocation = this.setMarkerLocation.bind(this); + this.map.savePolygonLocation = this.savePolygonLocation.bind(this); + this.map.saveLocation = this.saveLocation.bind(this); + let pageSize = this.settings.mapPageSize; + if (isDefinedAndNotNull(this.ctx.widgetConfig.pageSize)) { + pageSize = Math.max(pageSize, this.ctx.widgetConfig.pageSize); + } + this.pageLink = { + page: 0, + pageSize, + textSearch: null, + dynamic: true + }; + this.map.setLoading(true); + this.ctx.defaultSubscription.paginatedDataSubscriptionUpdated.subscribe(() => { + this.map.resetState(); + }); + this.ctx.defaultSubscription.subscribeAllForPaginatedData(this.pageLink, null); + if (this.updatePending) { + this.update(); + } + if (this.latestUpdatePending) { + this.latestDataUpdate(); + } + if (this.resizePending) { + this.resize(); + } + if (mapLoaded) { + mapLoaded(this.map); + } + } }); - this.ctx.defaultSubscription.subscribeAllForPaginatedData(this.pageLink, null); } map: LeafletMap; @@ -233,27 +255,27 @@ export class MapWidgetController implements MapWidgetInterface { } } - initSettings(settings: UnitedMapSettings, isEditMap?: boolean): WidgetUnitedMapSettings { + async initSettings(settings: UnitedMapSettings, isEditMap?: boolean): Promise { const functionParams = ['data', 'dsData', 'dsIndex']; this.provider = settings.provider || this.mapProvider; const parsedOptions: Partial = { provider: this.provider, - parsedLabelFunction: parseFunction(settings.labelFunction, functionParams), - parsedTooltipFunction: parseFunction(settings.tooltipFunction, functionParams), - parsedColorFunction: parseFunction(settings.colorFunction, functionParams), - parsedColorPointFunction: parseFunction(settings.colorPointFunction, functionParams), - parsedStrokeOpacityFunction: parseFunction(settings.strokeOpacityFunction, functionParams), - parsedStrokeWeightFunction: parseFunction(settings.strokeWeightFunction, functionParams), - parsedPolygonLabelFunction: parseFunction(settings.polygonLabelFunction, functionParams), - parsedPolygonColorFunction: parseFunction(settings.polygonColorFunction, functionParams), - parsedPolygonStrokeColorFunction: parseFunction(settings.polygonStrokeColorFunction, functionParams), - parsedPolygonTooltipFunction: parseFunction(settings.polygonTooltipFunction, functionParams), - parsedCircleLabelFunction: parseFunction(settings.circleLabelFunction, functionParams), - parsedCircleStrokeColorFunction: parseFunction(settings.circleStrokeColorFunction, functionParams), - parsedCircleFillColorFunction: parseFunction(settings.circleFillColorFunction, functionParams), - parsedCircleTooltipFunction: parseFunction(settings.circleTooltipFunction, functionParams), - parsedMarkerImageFunction: parseFunction(settings.markerImageFunction, ['data', 'images', 'dsData', 'dsIndex']), - parsedClusterMarkerFunction: parseFunction(settings.clusterMarkerFunction, ['data', 'childCount']), + parsedLabelFunction: await firstValueFrom(parseTbFunction(this.ctx.http, settings.labelFunction, functionParams)), + parsedTooltipFunction: await firstValueFrom(parseTbFunction(this.ctx.http, settings.tooltipFunction, functionParams)), + parsedColorFunction: await firstValueFrom(parseTbFunction(this.ctx.http, settings.colorFunction, functionParams)), + parsedColorPointFunction: await firstValueFrom(parseTbFunction(this.ctx.http, settings.colorPointFunction, functionParams)), + parsedStrokeOpacityFunction: await firstValueFrom(parseTbFunction(this.ctx.http, settings.strokeOpacityFunction, functionParams)), + parsedStrokeWeightFunction: await firstValueFrom(parseTbFunction(this.ctx.http, settings.strokeWeightFunction, functionParams)), + parsedPolygonLabelFunction: await firstValueFrom(parseTbFunction(this.ctx.http, settings.polygonLabelFunction, functionParams)), + parsedPolygonColorFunction: await firstValueFrom(parseTbFunction(this.ctx.http, settings.polygonColorFunction, functionParams)), + parsedPolygonStrokeColorFunction: await firstValueFrom(parseTbFunction(this.ctx.http, settings.polygonStrokeColorFunction, functionParams)), + parsedPolygonTooltipFunction: await firstValueFrom(parseTbFunction(this.ctx.http, settings.polygonTooltipFunction, functionParams)), + parsedCircleLabelFunction: await firstValueFrom(parseTbFunction(this.ctx.http, settings.circleLabelFunction, functionParams)), + parsedCircleStrokeColorFunction: await firstValueFrom(parseTbFunction(this.ctx.http, settings.circleStrokeColorFunction, functionParams)), + parsedCircleFillColorFunction: await firstValueFrom(parseTbFunction(this.ctx.http, settings.circleFillColorFunction, functionParams)), + parsedCircleTooltipFunction: await firstValueFrom(parseTbFunction(this.ctx.http, settings.circleTooltipFunction, functionParams)), + parsedMarkerImageFunction: await firstValueFrom(parseTbFunction(this.ctx.http, settings.markerImageFunction, ['data', 'images', 'dsData', 'dsIndex'])), + parsedClusterMarkerFunction: await firstValueFrom(parseTbFunction(this.ctx.http, settings.clusterMarkerFunction, ['data', 'childCount'])), // labelColor: this.ctx.widgetConfig.color, // polygonLabelColor: this.ctx.widgetConfig.color, polygonKeyName: (settings as any).polKeyName ? (settings as any).polKeyName : settings.polygonKeyName, @@ -277,20 +299,36 @@ export class MapWidgetController implements MapWidgetInterface { } update() { + if (this.map) { + this.updatePending = false; this.map.updateData(this.drawRoutes); this.map.setLoading(false); + } else { + this.updatePending = true; + } } latestDataUpdate() { - this.map.updateData(this.drawRoutes); + if (this.map) { + this.latestUpdatePending = false; + this.map.updateData(this.drawRoutes); + } else { + this.latestUpdatePending = true; + } } resize() { - this.map.onResize(); - this.map?.invalidateSize(); + if (this.map) { + this.resizePending = false; + this.map.onResize(); + this.map.invalidateSize(); + } else { + this.resizePending = true; + } } destroy() { + this.destroyed = true; if (this.map) { this.map.remove(); } 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 125bf4b5c4..d47ed8f110 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,11 @@ /// import L from 'leaflet'; -import { - GenericFunction, - ShowTooltipAction, WidgetToolipSettings -} from './map-models'; +import { GenericFunction, ShowTooltipAction, WidgetToolipSettings } from './map-models'; import { Datasource, FormattedData } from '@app/shared/models/widget.models'; -import { fillDataPattern, isDefinedAndNotNull, isString, processDataPattern, safeExecute } from '@core/utils'; +import { fillDataPattern, isDefinedAndNotNull, isString, processDataPattern, safeExecuteTbFunction } from '@core/utils'; import { parseWithTranslation } from '@home/components/widget/lib/maps/common-maps-utils'; +import { CompiledTbFunction } from '@shared/models/js-function.models'; export function createTooltip(target: L.Layer, settings: Partial, @@ -90,7 +88,7 @@ export function isJSON(data: string): boolean { export interface LabelSettings { showLabel: boolean; useLabelFunction: boolean; - parsedLabelFunction: GenericFunction; + parsedLabelFunction: CompiledTbFunction; label: string; } @@ -98,7 +96,7 @@ export function entitiesParseName(entities: FormattedData[], labelSettings: Labe const div = document.createElement('div'); for (const entity of entities) { if (labelSettings?.showLabel) { - const pattern = labelSettings.useLabelFunction ? safeExecute(labelSettings.parsedLabelFunction, + const pattern = labelSettings.useLabelFunction ? safeExecuteTbFunction(labelSettings.parsedLabelFunction, [entity, entities, entity.dsIndex]) : labelSettings.label; const markerLabelText = parseWithTranslation.prepareProcessPattern(pattern, true); const replaceInfoLabelMarker = processDataPattern(pattern, entity); 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 e41928d9e9..d62b2b9217 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 @@ -19,7 +19,13 @@ import { MarkerIconInfo, MarkerIconReadyFunction, MarkerImageInfo, WidgetMarkers import { bindPopupActions, createTooltip } from './maps-utils'; import { loadImageWithAspect, parseWithTranslation } from './common-maps-utils'; import tinycolor from 'tinycolor2'; -import { fillDataPattern, isDefined, isDefinedAndNotNull, processDataPattern, safeExecute } from '@core/utils'; +import { + fillDataPattern, + isDefined, + isDefinedAndNotNull, + processDataPattern, + safeExecuteTbFunction +} from '@core/utils'; import LeafletMap from './leaflet-map'; import { FormattedData } from '@shared/models/widget.models'; import { ImagePipe } from '@shared/pipe/image.pipe'; @@ -101,7 +107,7 @@ export class Marker { updateMarkerTooltip(data: FormattedData) { if (!this.map.markerTooltipText || this.settings.useTooltipFunction) { const pattern = this.settings.useTooltipFunction ? - safeExecute(this.settings.parsedTooltipFunction, [this.data, this.dataSources, this.data.dsIndex]) : this.settings.tooltipPattern; + safeExecuteTbFunction(this.settings.parsedTooltipFunction, [this.data, this.dataSources, this.data.dsIndex]) : this.settings.tooltipPattern; this.map.markerTooltipText = parseWithTranslation.prepareProcessPattern(pattern, true); this.map.replaceInfoTooltipMarker = processDataPattern(this.map.markerTooltipText, data); } @@ -123,7 +129,7 @@ export class Marker { if (settings.showLabel) { if (!this.map.markerLabelText || settings.useLabelFunction) { const pattern = settings.useLabelFunction ? - safeExecute(settings.parsedLabelFunction, [this.data, this.dataSources, this.data.dsIndex]) : settings.label; + safeExecuteTbFunction(settings.parsedLabelFunction, [this.data, this.dataSources, this.data.dsIndex]) : settings.label; this.map.markerLabelText = parseWithTranslation.prepareProcessPattern(pattern, true); this.map.replaceInfoLabelMarker = processDataPattern(this.map.markerLabelText, this.data); } @@ -165,11 +171,11 @@ export class Marker { return; } const currentImage: MarkerImageInfo = this.settings.useMarkerImageFunction ? - safeExecute(this.settings.parsedMarkerImageFunction, + safeExecuteTbFunction(this.settings.parsedMarkerImageFunction, [this.data, this.settings.markerImages, this.dataSources, this.data.dsIndex]) : this.settings.currentImage; let currentColor = this.settings.tinyColor; if (this.settings.useColorFunction) { - const functionColor = safeExecute(this.settings.parsedColorFunction, + const functionColor = safeExecuteTbFunction(this.settings.parsedColorFunction, [this.data, this.dataSources, this.data.dsIndex]); if (isDefinedAndNotNull(functionColor)) { currentColor = tinycolor(functionColor); 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 aef95731e5..cb9ba7c35f 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,13 +16,10 @@ import L, { LatLngExpression, LeafletMouseEvent } from 'leaflet'; import { createTooltip, isCutPolygon } from './maps-utils'; -import { - functionValueCalculator, - parseWithTranslation -} from './common-maps-utils'; +import { functionValueCalculator, parseWithTranslation } from './common-maps-utils'; import { WidgetPolygonSettings } from './map-models'; import { FormattedData } from '@shared/models/widget.models'; -import { fillDataPattern, processDataPattern, safeExecute } from '@core/utils'; +import { fillDataPattern, processDataPattern, safeExecuteTbFunction } from '@core/utils'; import LeafletMap from '@home/components/widget/lib/maps/leaflet-map'; export class Polygon { @@ -92,7 +89,7 @@ export class Polygon { updateTooltip(data: FormattedData) { const pattern = this.settings.usePolygonTooltipFunction ? - safeExecute(this.settings.parsedPolygonTooltipFunction, [this.data, this.dataSources, this.data.dsIndex]) : + safeExecuteTbFunction(this.settings.parsedPolygonTooltipFunction, [this.data, this.dataSources, this.data.dsIndex]) : this.settings.polygonTooltipPattern; this.tooltip.setContent(parseWithTranslation.parseTemplate(pattern, data, true)); } @@ -102,7 +99,7 @@ export class Polygon { if (settings.showPolygonLabel) { if (!this.map.polygonLabelText || settings.usePolygonLabelFunction) { const pattern = settings.usePolygonLabelFunction ? - safeExecute(settings.parsedPolygonLabelFunction, + safeExecuteTbFunction(settings.parsedPolygonLabelFunction, [this.data, this.dataSources, this.data.dsIndex]) : settings.polygonLabel; this.map.polygonLabelText = parseWithTranslation.prepareProcessPattern(pattern, true); this.map.replaceInfoLabelPolygon = processDataPattern(this.map.polygonLabelText, this.data); diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/maps/providers/image-map.ts b/ui-ngx/src/app/modules/home/components/widget/lib/maps/providers/image-map.ts index 8cf1822ad5..0abcc9f7c8 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/maps/providers/image-map.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/maps/providers/image-map.ts @@ -23,16 +23,17 @@ import { PosFunction, WidgetUnitedMapSettings } from '../map-models'; -import { Observable, of, ReplaySubject, switchMap } from 'rxjs'; +import { forkJoin, Observable, of, ReplaySubject, switchMap } from 'rxjs'; import { catchError } from 'rxjs/operators'; import { calculateNewPointCoordinate, loadImageWithAspect } from '@home/components/widget/lib/maps/common-maps-utils'; import { WidgetContext } from '@home/models/widget-component.models'; import { DataSet, DatasourceType, FormattedData, widgetType } from '@shared/models/widget.models'; import { DataKeyType } from '@shared/models/telemetry/telemetry.models'; import { WidgetSubscriptionOptions } from '@core/api/widget-api.models'; -import { isDefinedAndNotNull, isEmptyStr, isNotEmptyStr, parseFunction } from '@core/utils'; +import { isDefinedAndNotNull, isEmptyStr, isNotEmptyStr, parseFunction, parseTbFunction } from '@core/utils'; import { EntityDataPageLink } from '@shared/models/query/query.models'; import { ImagePipe } from '@shared/pipe/image.pipe'; +import { CompiledTbFunction } from '@shared/models/js-function.models'; const maxZoom = 4; // ? @@ -43,13 +44,20 @@ export class ImageMap extends LeafletMap { width = 0; height = 0; imageUrl: string; - posFunction: PosFunction; + posFunction: CompiledTbFunction; constructor(ctx: WidgetContext, $container: HTMLElement, options: WidgetUnitedMapSettings) { super(ctx, $container, options); - this.posFunction = parseFunction(options.posFunction, - ['origXPos', 'origYPos', 'data', 'dsData', 'dsIndex', 'aspect']) as PosFunction; - this.mapImage(options).subscribe((mapImage) => { + + const initData = { + posFunction: parseTbFunction(this.ctx.http, options.posFunction, + ['origXPos', 'origYPos', 'data', 'dsData', 'dsIndex', 'aspect']), + mapImage: this.mapImage(options) + }; + + forkJoin(initData).subscribe(inited => { + this.posFunction = inited.posFunction; + const mapImage = inited.mapImage; this.imageUrl = mapImage.imageUrl; this.aspect = mapImage.aspect; if (mapImage.update) { @@ -272,7 +280,7 @@ export class ImageMap extends LeafletMap { convertPosition(data: FormattedData, dsData: FormattedData[]): L.LatLng { const position = this.extractPosition(data); if (position) { - const converted = this.posFunction(position.x, position.y, data, dsData, data.dsIndex, this.aspect) || {x: 0, y: 0}; + const converted = this.posFunction.execute(position.x, position.y, data, dsData, data.dsIndex, this.aspect) || {x: 0, y: 0}; return this.positionToLatLng(converted); } else { return null; diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/map/circle-settings.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/map/circle-settings.component.html index d719c84e9c..57ce448999 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/map/circle-settings.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/map/circle-settings.component.html @@ -62,6 +62,7 @@ { - this.historicalData = formattedDataArrayFromDatasourceData(this.ctx.data).map( - item => this.clearIncorrectFirsLastDatapoint(item)).filter(arr => arr.length); - this.interpolatedTimeData.length = 0; - this.formattedInterpolatedTimeData.length = 0; - const prevMinTime = this.minTime; - const prevMaxTime = this.maxTime; - this.calculateIntervals(); - const currentTime = this.calculateCurrentTime(prevMinTime, prevMaxTime); - if (currentTime !== this.currentTime) { - this.timeUpdated(currentTime); - } - this.mapWidget.map.map?.invalidateSize(); - this.mapWidget.map.setLoading(false); - this.cd.detectChanges(); + this.update(); }; subscription.callbacks.onLatestDataUpdated = () => { - this.formattedLatestData = formattedDataFormDatasourceData(this.ctx.latestData); - this.updateCurrentData(); + this.latestDataUpdate(); }; + from(this.initializeFunctions()).subscribe(() => { + this.initialized = true; + if (this.updatePending) { + this.updateCurrentData(); + } + }); + } ngAfterViewInit() { import('@home/components/widget/lib/maps/map-widget2').then( (mod) => { - this.mapWidget = new mod.MapWidgetController(MapProviders.openstreet, false, this.ctx, this.mapContainer.nativeElement); + this.mapWidget = new mod.MapWidgetController(MapProviders.openstreet, false, this.ctx, this.mapContainer.nativeElement, false, + () => { + if (this.mapWidgetUpdatePending) { + this.updateMapWidget(); + } + } + ); this.mapResize$ = new ResizeObserver(() => { this.mapWidget.resize(); }); @@ -177,22 +177,65 @@ export class TripAnimationComponent implements OnInit, AfterViewInit, OnDestroy this.updateCurrentData(); } - private updateCurrentData() { - let currentPosition = this.formattedCurrentPosition; - if (this.formattedLatestData.length) { - currentPosition = mergeFormattedData(this.formattedCurrentPosition, this.formattedLatestData); + private async initializeFunctions(): Promise { + this.settings.parsedPointAsAnchorFunction = await firstValueFrom(parseTbFunction(this.ctx.http, this.settings.pointAsAnchorFunction, ['data', 'dsData', 'dsIndex'])); + this.settings.parsedTooltipFunction = await firstValueFrom(parseTbFunction(this.ctx.http, this.settings.tooltipFunction, ['data', 'dsData', 'dsIndex'])); + this.settings.parsedLabelFunction = await firstValueFrom(parseTbFunction(this.ctx.http, this.settings.labelFunction, ['data', 'dsData', 'dsIndex'])); + this.settings.parsedColorPointFunction = await firstValueFrom(parseTbFunction(this.ctx.http, this.settings.colorPointFunction, ['data', 'dsData', 'dsIndex'])); + } + + private update() { + this.historicalData = formattedDataArrayFromDatasourceData(this.ctx.data).map( + item => this.clearIncorrectFirsLastDatapoint(item)).filter(arr => arr.length); + this.interpolatedTimeData.length = 0; + this.formattedInterpolatedTimeData.length = 0; + const prevMinTime = this.minTime; + const prevMaxTime = this.maxTime; + this.calculateIntervals(); + const currentTime = this.calculateCurrentTime(prevMinTime, prevMaxTime); + if (currentTime !== this.currentTime) { + this.timeUpdated(currentTime); } - this.calcLabel(currentPosition); - this.calcMainTooltip(currentPosition); - if (this.mapWidget && this.mapWidget.map && this.mapWidget.map.map) { - this.mapWidget.map.updateFromData(true, currentPosition, this.formattedInterpolatedTimeData, (trip) => { - this.activeTrip = trip; - this.timeUpdated(this.currentTime); - this.cd.markForCheck(); - }); - if (this.settings.showPoints) { - this.mapWidget.map.updatePoints(this.formattedInterpolatedTimeData, this.calcTooltip); + this.updateMapWidget(); + } + + private latestDataUpdate() { + this.formattedLatestData = formattedDataFormDatasourceData(this.ctx.latestData); + this.updateCurrentData(); + } + + private updateMapWidget() { + if (this.mapWidget?.map) { + this.mapWidgetUpdatePending = false; + this.mapWidget.map.map?.invalidateSize(); + this.mapWidget.map.setLoading(false); + this.cd.detectChanges(); + } else { + this.mapWidgetUpdatePending = true; + } + } + + private updateCurrentData() { + if (this.initialized) { + this.updatePending = false; + let currentPosition = this.formattedCurrentPosition; + if (this.formattedLatestData.length) { + currentPosition = mergeFormattedData(this.formattedCurrentPosition, this.formattedLatestData); } + this.calcLabel(currentPosition); + this.calcMainTooltip(currentPosition); + if (this.mapWidget?.map?.map) { + this.mapWidget.map.updateFromData(true, currentPosition, this.formattedInterpolatedTimeData, (trip) => { + this.activeTrip = trip; + this.timeUpdated(this.currentTime); + this.cd.markForCheck(); + }); + if (this.settings.showPoints) { + this.mapWidget.map.updatePoints(this.formattedInterpolatedTimeData, this.calcTooltip); + } + } + } else { + this.updatePending = true; } } @@ -235,7 +278,7 @@ export class TripAnimationComponent implements OnInit, AfterViewInit, OnDestroy if (this.useAnchors) { const anchorDate = Object.entries(_.union(this.interpolatedTimeData)[0]); this.anchors = anchorDate - .filter((data: [string, FormattedData], tsIndex) => safeExecute(this.settings.parsedPointAsAnchorFunction, [data[1], + .filter((data: [string, FormattedData], tsIndex) => safeExecuteTbFunction(this.settings.parsedPointAsAnchorFunction, [data[1], this.formattedInterpolatedTimeData.map(ds => ds[tsIndex]), data[1].dsIndex])) .map(data => parseInt(data[0], 10)); } @@ -244,7 +287,7 @@ export class TripAnimationComponent implements OnInit, AfterViewInit, OnDestroy calcTooltip = (point: FormattedData, points: FormattedData[]): string => { const data = point ? point : this.activeTrip; const tooltipPattern: string = this.settings.useTooltipFunction ? - safeExecute(this.settings.parsedTooltipFunction, + safeExecuteTbFunction(this.settings.parsedTooltipFunction, [data, points, point.dsIndex]) : this.settings.tooltipPattern; return parseWithTranslation.parseTemplate(tooltipPattern, data, true); } @@ -261,7 +304,7 @@ export class TripAnimationComponent implements OnInit, AfterViewInit, OnDestroy if (this.activeTrip) { const data = points[this.activeTrip.dsIndex]; const labelText: string = this.settings.useLabelFunction ? - safeExecute(this.settings.parsedLabelFunction, [data, points, data.dsIndex]) : this.settings.label; + safeExecuteTbFunction(this.settings.parsedLabelFunction, [data, points, data.dsIndex]) : this.settings.label; this.label = this.sanitizer.bypassSecurityTrustHtml(parseWithTranslation.parseTemplate(labelText, data, true)); } }