From 73a654d7840c2290877a3eaf7f765f4d3efb45f6 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Thu, 27 Feb 2025 18:43:19 +0200 Subject: [PATCH] UI: Trip map settings. --- .../lib/maps/data-layer/trips-data-layer.ts | 4 +- .../widget/lib/maps/models/map.models.ts | 35 +++- .../lib/maps/models/marker-shape.models.ts | 71 +++++++- .../panels/map-timeline-panel.component.html | 11 ++ .../panels/map-timeline-panel.component.scss | 8 + .../panels/map-timeline-panel.component.ts | 36 +++- ...data-layer-pattern-settings.component.html | 2 +- .../data-layer-pattern-settings.component.ts | 3 + .../map/map-action-button-row.component.html | 2 +- .../map/map-action-button-row.component.ts | 2 +- ...map-action-buttons-settings.component.html | 2 +- .../map-action-buttons-settings.component.ts | 2 +- .../map/map-data-layer-dialog.component.html | 172 +++++++++++++++--- .../map/map-data-layer-dialog.component.ts | 124 +++++++++++-- .../map/map-data-layer-row.component.html | 12 +- .../map/map-data-layer-row.component.ts | 30 ++- .../common/map/map-data-layers.component.html | 4 +- .../common/map/map-data-layers.component.ts | 4 + .../common/map/map-settings.component.html | 9 + .../map/map-settings.component.models.ts | 4 +- .../common/map/map-settings.component.ts | 12 +- .../map/marker-shape-settings.component.ts | 10 +- .../common/map/marker-shapes.component.ts | 13 +- .../map/trip-timeline-settings.component.html | 81 +++++++++ .../map/trip-timeline-settings.component.ts | 172 ++++++++++++++++++ .../common/widget-settings-common.module.ts | 4 + .../map/map-widget-settings.component.html | 1 + .../map/map-widget-settings.component.ts | 12 +- .../assets/locale/locale.constant-en_US.json | 31 +++- ui-ngx/src/assets/markers/tripShape1.svg | 4 + ui-ngx/src/assets/markers/tripShape10.svg | 10 + ui-ngx/src/assets/markers/tripShape2.svg | 15 ++ ui-ngx/src/assets/markers/tripShape3.svg | 9 + ui-ngx/src/assets/markers/tripShape4.svg | 9 + ui-ngx/src/assets/markers/tripShape5.svg | 10 + ui-ngx/src/assets/markers/tripShape6.svg | 10 + ui-ngx/src/assets/markers/tripShape7.svg | 11 ++ ui-ngx/src/assets/markers/tripShape8.svg | 9 + ui-ngx/src/assets/markers/tripShape9.svg | 10 + 39 files changed, 882 insertions(+), 88 deletions(-) create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/trip-timeline-settings.component.html create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/trip-timeline-settings.component.ts create mode 100644 ui-ngx/src/assets/markers/tripShape1.svg create mode 100644 ui-ngx/src/assets/markers/tripShape10.svg create mode 100644 ui-ngx/src/assets/markers/tripShape2.svg create mode 100644 ui-ngx/src/assets/markers/tripShape3.svg create mode 100644 ui-ngx/src/assets/markers/tripShape4.svg create mode 100644 ui-ngx/src/assets/markers/tripShape5.svg create mode 100644 ui-ngx/src/assets/markers/tripShape6.svg create mode 100644 ui-ngx/src/assets/markers/tripShape7.svg create mode 100644 ui-ngx/src/assets/markers/tripShape8.svg create mode 100644 ui-ngx/src/assets/markers/tripShape9.svg diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/maps/data-layer/trips-data-layer.ts b/ui-ngx/src/app/modules/home/components/widget/lib/maps/data-layer/trips-data-layer.ts index 354fa4eb0f..63824365ca 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/maps/data-layer/trips-data-layer.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/maps/data-layer/trips-data-layer.ts @@ -331,7 +331,7 @@ export class TbTripsDataLayer extends TbMapDataLayer d[0].$datasource.mapDataIds.includes(this.mapDataId)).map( + tripsData.filter(d => !!d.length && d[0].$datasource.mapDataIds.includes(this.mapDataId)).map( item => this.clearIncorrectFirsLastDatapoint(item)).filter(arr => arr.length); this.latestTripsData = tripsLatestData.filter(d => d.$datasource.mapDataIds.includes(this.mapDataId)); this.rawTripsData.forEach((dataSource) => { @@ -396,7 +396,7 @@ export class TbTripsDataLayer extends TbMapDataLayer { - return createColorMarkerIconElement(this.getCtx().$injector.get(MatIconRegistry), this.getCtx().$injector.get(DomSanitizer), icon, color).pipe( + return createColorMarkerIconElement(this.getCtx().$injector.get(MatIconRegistry), this.getCtx().$injector.get(DomSanitizer), icon, color, true).pipe( map((element) => { element.style.transform = `rotate(${rotationAngle}deg)`; return { diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/maps/models/map.models.ts b/ui-ngx/src/app/modules/home/components/widget/lib/maps/models/map.models.ts index 2361440828..f93eab6dbc 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/maps/models/map.models.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/maps/models/map.models.ts @@ -157,7 +157,7 @@ export const defaultBaseDataLayerSettings = (mapType: MapType): Partial { if (!dataLayer.dsType || ![DatasourceType.function, DatasourceType.device, DatasourceType.entity].includes(dataLayer.dsType)) { @@ -307,13 +307,13 @@ const defaultMarkerYPosFunction = 'var value = prevValue || 0.3;\n' + '}\n' + 'return value;'; -export const defaultMarkersDataLayerSettings = (mapType: MapType, functionsOnly = false): MarkersDataLayerSettings => mergeDeep({ +const defaultMarkersDataSourceSettings = (mapType: MapType, timeSeries = false, functionsOnly = false): Partial => ({ dsType: functionsOnly ? DatasourceType.function : DatasourceType.entity, dsLabel: functionsOnly ? 'First point' : '', xKey: { name: functionsOnly ? 'f(x)' : (MapType.geoMap === mapType ? 'latitude' : 'xPos'), label: MapType.geoMap === mapType ? 'latitude' : 'xPos', - type: functionsOnly ? DataKeyType.function : DataKeyType.attribute, + type: functionsOnly ? DataKeyType.function : (timeSeries ? DataKeyType.timeseries : DataKeyType.attribute), funcBody: functionsOnly ? (MapType.geoMap === mapType ? defaultMarkerLatitudeFunction : defaultMarkerXPosFunction) : undefined, settings: {}, color: materialColors[0].value @@ -321,12 +321,16 @@ export const defaultMarkersDataLayerSettings = (mapType: MapType, functionsOnly yKey: { name: functionsOnly ? 'f(x)' : (MapType.geoMap === mapType ? 'longitude' : 'yPos'), label: MapType.geoMap === mapType ? 'longitude' : 'yPos', - type: functionsOnly ? DataKeyType.function : DataKeyType.attribute, + type: functionsOnly ? DataKeyType.function : (timeSeries ? DataKeyType.timeseries : DataKeyType.attribute), funcBody: functionsOnly ? (MapType.geoMap === mapType ? defaultMarkerLongitudeFunction : defaultMarkerYPosFunction) : undefined, settings: {}, color: materialColors[0].value } -} as MarkersDataLayerSettings, defaultBaseMarkersDataLayerSettings(mapType) as MarkersDataLayerSettings); +}); + +export const defaultMarkersDataLayerSettings = (mapType: MapType, functionsOnly = false): MarkersDataLayerSettings => mergeDeep( + defaultMarkersDataSourceSettings(mapType, false, functionsOnly) as MarkersDataLayerSettings, + defaultBaseMarkersDataLayerSettings(mapType) as MarkersDataLayerSettings); export const defaultBaseMarkersDataLayerSettings = (mapType: MapType): Partial => mergeDeep({ markerType: MarkerType.shape, @@ -378,8 +382,8 @@ export const pathDecoratorSymbols = Object.keys(PathDecoratorSymbol) as PathDeco export const pathDecoratorSymbolTranslationMap = new Map( [ - [PathDecoratorSymbol.arrowHead, 'widgets.maps.data-layer.decorator-symbol-arrow-head'], - [PathDecoratorSymbol.dash, 'widgets.maps.data-layer.decorator-symbol-dash'] + [PathDecoratorSymbol.arrowHead, 'widgets.maps.data-layer.path.decorator-symbol-arrow-head'], + [PathDecoratorSymbol.dash, 'widgets.maps.data-layer.path.decorator-symbol-dash'] ] ); @@ -402,6 +406,10 @@ export interface TripsDataLayerSettings extends MarkersDataLayerSettings { pointTooltip?: DataLayerTooltipSettings; } +export const defaultTripsDataLayerSettings = (mapType: MapType, functionsOnly = false): TripsDataLayerSettings => mergeDeep( + defaultMarkersDataSourceSettings(mapType, true, functionsOnly) as TripsDataLayerSettings, + defaultBaseTripsDataLayerSettings(mapType) as TripsDataLayerSettings); + export const defaultBaseTripsDataLayerSettings = (mapType: MapType): Partial => mergeDeep( defaultBaseMarkersDataLayerSettings(mapType), { @@ -412,6 +420,15 @@ export const defaultBaseTripsDataLayerSettings = (mapType: MapType): Partial { switch (dataLayerType) { + case 'trips': + return defaultTripsDataLayerSettings(mapType, functionsOnly); case 'markers': return defaultMarkersDataLayerSettings(mapType, functionsOnly); case 'polygons': @@ -523,6 +542,8 @@ export const defaultMapDataLayerSettings = (mapType: MapType, dataLayerType: Map export const defaultBaseMapDataLayerSettings = (mapType: MapType, dataLayerType: MapDataLayerType): T => { switch (dataLayerType) { + case 'trips': + return defaultBaseTripsDataLayerSettings(mapType) as T; case 'markers': return defaultBaseMarkersDataLayerSettings(mapType) as T; case 'polygons': diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/maps/models/marker-shape.models.ts b/ui-ngx/src/app/modules/home/components/widget/lib/maps/models/marker-shape.models.ts index 7abf0e19cd..1948ff6fe7 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/maps/models/marker-shape.models.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/maps/models/marker-shape.models.ts @@ -32,7 +32,17 @@ export enum MarkerShape { markerShape7 = 'markerShape7', markerShape8 = 'markerShape8', markerShape9 = 'markerShape9', - markerShape10 = 'markerShape10' + markerShape10 = 'markerShape10', + tripMarkerShape1 = 'tripMarkerShape1', + tripMarkerShape2 = 'tripMarkerShape2', + tripMarkerShape3 = 'tripMarkerShape3', + tripMarkerShape4 = 'tripMarkerShape4', + tripMarkerShape5 = 'tripMarkerShape5', + tripMarkerShape6 = 'tripMarkerShape6', + tripMarkerShape7 = 'tripMarkerShape7', + tripMarkerShape8 = 'tripMarkerShape8', + tripMarkerShape9 = 'tripMarkerShape9', + tripMarkerShape10 = 'tripMarkerShape10' } export const markerShapeMap = new Map( @@ -46,21 +56,63 @@ export const markerShapeMap = new Map( [MarkerShape.markerShape7, '/assets/markers/shape7.svg'], [MarkerShape.markerShape8, '/assets/markers/shape8.svg'], [MarkerShape.markerShape9, '/assets/markers/shape9.svg'], - [MarkerShape.markerShape10, '/assets/markers/shape10.svg'] + [MarkerShape.markerShape10, '/assets/markers/shape10.svg'], + [MarkerShape.tripMarkerShape1, '/assets/markers/tripShape1.svg'], + [MarkerShape.tripMarkerShape2, '/assets/markers/tripShape2.svg'], + [MarkerShape.tripMarkerShape3, '/assets/markers/tripShape3.svg'], + [MarkerShape.tripMarkerShape4, '/assets/markers/tripShape4.svg'], + [MarkerShape.tripMarkerShape5, '/assets/markers/tripShape5.svg'], + [MarkerShape.tripMarkerShape6, '/assets/markers/tripShape6.svg'], + [MarkerShape.tripMarkerShape7, '/assets/markers/tripShape7.svg'], + [MarkerShape.tripMarkerShape8, '/assets/markers/tripShape8.svg'], + [MarkerShape.tripMarkerShape9, '/assets/markers/tripShape9.svg'], + [MarkerShape.tripMarkerShape10, '/assets/markers/tripShape10.svg'] ] ); +export const markerShapes = [ + MarkerShape.markerShape1, + MarkerShape.markerShape2, + MarkerShape.markerShape3, + MarkerShape.markerShape4, + MarkerShape.markerShape5, + MarkerShape.markerShape6, + MarkerShape.markerShape7, + MarkerShape.markerShape8, + MarkerShape.markerShape9, + MarkerShape.markerShape10 +]; + +export const tripMarkerShapes = [ + MarkerShape.tripMarkerShape1, + MarkerShape.tripMarkerShape2, + MarkerShape.tripMarkerShape3, + MarkerShape.tripMarkerShape4, + MarkerShape.tripMarkerShape5, + MarkerShape.tripMarkerShape6, + MarkerShape.tripMarkerShape7, + MarkerShape.tripMarkerShape8, + MarkerShape.tripMarkerShape9, + MarkerShape.tripMarkerShape10 +]; + const createColorMarkerShape = (iconRegistry: MatIconRegistry, domSanitizer: DomSanitizer, shape: MarkerShape, color: tinycolor.Instance): Observable => { const markerAssetUrl = markerShapeMap.get(shape); const safeUrl = domSanitizer.bypassSecurityTrustResourceUrl(markerAssetUrl); return iconRegistry.getSvgIconFromUrl(safeUrl).pipe( map((svgElement) => { const colorElements = Array.from(svgElement.getElementsByClassName('marker-color')); + if (svgElement.classList.contains('marker-color')) { + colorElements.push(svgElement); + } colorElements.forEach(el => { el.setAttribute('fill', '#'+color.toHex()); el.setAttribute('fill-opacity', `${color.getAlpha()}`); }); const strokeElements = Array.from(svgElement.getElementsByClassName('marker-stroke')); + if (svgElement.classList.contains('marker-stroke')) { + strokeElements.push(svgElement); + } strokeElements.forEach(el => { el.setAttribute('stroke', '#'+color.toHex()); el.setAttribute('stroke-opacity', `${color.getAlpha()}`); @@ -80,10 +132,10 @@ export const createColorMarkerShapeURI = (iconRegistry: MatIconRegistry, domSani ); } -const createIconElement = (iconRegistry: MatIconRegistry, icon: string, size: number, color: tinycolor.Instance): Observable => { +const createIconElement = (iconRegistry: MatIconRegistry, icon: string, size: number, color: tinycolor.Instance, trip = false): Observable => { const isSvg = isSvgIcon(icon); const iconAlpha = color.getAlpha(); - const iconColor = tinycolor.mix(color.clone().setAlpha(1), tinycolor('rgba(0,0,0,0.38)')); + const iconColor = trip ? color : tinycolor.mix(color.clone().setAlpha(1), tinycolor('rgba(0,0,0,0.38)')); if (isSvg) { const [namespace, iconName] = splitIconName(icon); return iconRegistry @@ -122,14 +174,19 @@ const createIconElement = (iconRegistry: MatIconRegistry, icon: string, size: nu } } -export const createColorMarkerIconElement = (iconRegistry: MatIconRegistry, domSanitizer: DomSanitizer, icon: string, color: tinycolor.Instance): Observable => { - return createColorMarkerShape(iconRegistry, domSanitizer, MarkerShape.markerShape6, color).pipe( +const markerIconShape = MarkerShape.markerShape6; +const tripMarkerIconShape = MarkerShape.tripMarkerShape2; + +export const createColorMarkerIconElement = (iconRegistry: MatIconRegistry, domSanitizer: DomSanitizer, icon: string, color: tinycolor.Instance, + trip = false): Observable => { + return createColorMarkerShape(iconRegistry, domSanitizer, trip ? tripMarkerIconShape : markerIconShape, color).pipe( switchMap((svgElement) => { - return createIconElement(iconRegistry, icon, 12, color).pipe( + return createIconElement(iconRegistry, icon, trip ? 24 : 12, trip ? tinycolor('#fff') : color, trip).pipe( map((iconElement) => { let elements = svgElement.getElementsByClassName('marker-icon-container'); if (iconElement && elements.length) { const iconContainer = new G(elements[0] as SVGGElement); + iconContainer.clear(); iconContainer.add(iconElement); const box = iconElement.bbox(); iconElement.translate(-box.cx, -box.cy); diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/maps/panels/map-timeline-panel.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/maps/panels/map-timeline-panel.component.html index 543f07a3d7..57bf909613 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/maps/panels/map-timeline-panel.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/maps/panels/map-timeline-panel.component.html @@ -23,4 +23,15 @@ [step]="settings.timeStep"> +
+
+
+
+
2
+
+ + {{speedValue}}x + +
+
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/maps/panels/map-timeline-panel.component.scss b/ui-ngx/src/app/modules/home/components/widget/lib/maps/panels/map-timeline-panel.component.scss index 9e38394deb..7795e05523 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/maps/panels/map-timeline-panel.component.scss +++ b/ui-ngx/src/app/modules/home/components/widget/lib/maps/panels/map-timeline-panel.component.scss @@ -14,6 +14,14 @@ * limitations under the License. */ .tb-map-timeline-panel { + padding-top: 4px; + padding-left: 16px; + padding-right: 16px; display: flex; flex-direction: column; + .tb-timeline-controls { + display: flex; + flex-direction: row; + align-items: center; + } } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/maps/panels/map-timeline-panel.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/maps/panels/map-timeline-panel.component.ts index 33cac7c012..1cb76aceea 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/maps/panels/map-timeline-panel.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/maps/panels/map-timeline-panel.component.ts @@ -15,16 +15,18 @@ /// import { + ChangeDetectorRef, Component, ElementRef, - EventEmitter, - Input, + EventEmitter, Injector, + Input, OnChanges, OnDestroy, OnInit, Output, ViewEncapsulation } from '@angular/core'; import { TripTimelineSettings } from '@home/components/widget/lib/maps/models/map.models'; +import { DateFormatProcessor } from '@shared/models/widget-settings.models'; @Component({ selector: 'tb-map-timeline-panel', @@ -32,7 +34,7 @@ import { TripTimelineSettings } from '@home/components/widget/lib/maps/models/ma styleUrls: ['./map-timeline-panel.component.scss'], encapsulation: ViewEncapsulation.None }) -export class MapTimelinePanelComponent implements OnInit, OnDestroy { +export class MapTimelinePanelComponent implements OnInit, OnChanges, OnDestroy { @Input() settings: TripTimelineSettings; @@ -51,18 +53,42 @@ export class MapTimelinePanelComponent implements OnInit, OnDestroy { currentTime = 0; - constructor(public element: ElementRef) { + timestampFormat: DateFormatProcessor; + + speed: number; + + constructor(public element: ElementRef, + private cd: ChangeDetectorRef, + private injector: Injector) { } ngOnInit() { + if (this.settings.showTimestamp) { + this.timestampFormat = DateFormatProcessor.fromSettings(this.injector, this.settings.timestampFormat); + this.timestampFormat.update(this.currentTime); + } + this.speed = this.settings.speedOptions[0]; + } + + ngOnChanges() { + this.currentTime = this.min === Infinity ? 0 : this.min; + if (this.settings.showTimestamp) { + this.timestampFormat.update(this.currentTime); + this.cd.markForCheck(); + } } ngOnDestroy() { } public onTimeChange() { - //this.updateValueText(); + if (this.settings.showTimestamp) { + this.timestampFormat.update(this.currentTime); + this.cd.markForCheck(); + } this.timeChanged.next(this.currentTime); } + public speedUpdated() {} + } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/data-layer-pattern-settings.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/data-layer-pattern-settings.component.html index 71dbecab18..4286bf1c22 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/data-layer-pattern-settings.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/data-layer-pattern-settings.component.html @@ -23,7 +23,7 @@
- {{ (patternType === 'label' ? 'widgets.maps.data-layer.label' : 'widgets.maps.data-layer.tooltip') | translate }} + {{ patternTitle ? patternTitle : ((patternType === 'label' ? 'widgets.maps.data-layer.label' : 'widgets.maps.data-layer.tooltip') | translate) }} {{ 'widgets.maps.data-layer.pattern-type-pattern' | translate }} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/data-layer-pattern-settings.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/data-layer-pattern-settings.component.ts index 9a8835fa7b..e13e7eec1c 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/data-layer-pattern-settings.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/data-layer-pattern-settings.component.ts @@ -71,6 +71,9 @@ export class DataLayerPatternSettingsComponent implements OnInit, ControlValueAc @Input() patternType: 'label' | 'tooltip' = 'label'; + @Input() + patternTitle: string; + @Input() @coerceBoolean() hasTooltipOffset = false; diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/map-action-button-row.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/map-action-button-row.component.html index 70cc12dd27..8bd57487b0 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/map-action-button-row.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/map-action-button-row.component.html @@ -1,6 +1,6 @@ + + +
+ + + + + {{ 'widgets.maps.timeline.control-panel' | translate }} + + + + +
+
widgets.maps.timeline.time-step
+ + + ms + +
+
+
widgets.maps.timeline.speed-options
+ + + + {{ speed }} + + + +
+
+ + {{ 'widgets.maps.timeline.timestamp' | translate }} + + +
+
+ + + + + {{ 'widgets.maps.timeline.snap-to-real-location' | translate }} + + + + + + + + +
+
+
+
+
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/trip-timeline-settings.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/trip-timeline-settings.component.ts new file mode 100644 index 0000000000..fd9afb8740 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/trip-timeline-settings.component.ts @@ -0,0 +1,172 @@ +/// +/// Copyright © 2016-2025 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { Component, DestroyRef, forwardRef, Input, OnInit } from '@angular/core'; +import { + ControlValueAccessor, + NG_VALIDATORS, + NG_VALUE_ACCESSOR, + UntypedFormBuilder, + UntypedFormControl, + UntypedFormGroup, + Validator, + Validators +} from '@angular/forms'; +import { merge } from 'rxjs'; +import { WidgetService } from '@core/http/widget.service'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; +import { + DataLayerPatternSettings, + DataLayerPatternType, + DataLayerTooltipSettings, + dataLayerTooltipTriggers, + dataLayerTooltipTriggerTranslationMap, + pathDecoratorSymbols, pathDecoratorSymbolTranslationMap, + TripTimelineSettings +} from '@home/components/widget/lib/maps/models/map.models'; +import { coerceBoolean } from '@shared/decorators/coercion'; +import { MapSettingsContext } from '@home/components/widget/lib/settings/common/map/map-settings.component.models'; + +@Component({ + selector: 'tb-trip-timeline-settings', + templateUrl: './trip-timeline-settings.component.html', + styleUrls: ['./../../widget-settings.scss'], + providers: [ + { + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => TripTimelineSettingsComponent), + multi: true + }, + { + provide: NG_VALIDATORS, + useExisting: forwardRef(() => TripTimelineSettingsComponent), + multi: true + } + ] +}) +export class TripTimelineSettingsComponent implements OnInit, ControlValueAccessor, Validator { + + settingsExpanded = false; + + functionScopeVariables = this.widgetService.getWidgetScopeVariables(); + + @Input() + disabled: boolean; + + private modelValue: TripTimelineSettings; + + private propagateChange = null; + + public tripTimelineSettingsFormGroup: UntypedFormGroup; + + constructor(private fb: UntypedFormBuilder, + private widgetService: WidgetService, + private destroyRef: DestroyRef) { + } + + ngOnInit(): void { + + this.tripTimelineSettingsFormGroup = this.fb.group({ + showTimelineControl: [null], + timeStep: [null, [Validators.required, Validators.min(1)]], + speedOptions: [null, [Validators.required]], + showTimestamp: [null], + timestampFormat: [null, [Validators.required]], + snapToRealLocation: [null], + locationSnapFilter: [null, [Validators.required]] + }); + + this.tripTimelineSettingsFormGroup.valueChanges.pipe( + takeUntilDestroyed(this.destroyRef) + ).subscribe(() => { + this.updateModel(); + }); + merge(this.tripTimelineSettingsFormGroup.get('showTimelineControl').valueChanges, + this.tripTimelineSettingsFormGroup.get('showTimestamp').valueChanges, + this.tripTimelineSettingsFormGroup.get('snapToRealLocation').valueChanges + ).pipe( + takeUntilDestroyed(this.destroyRef) + ).subscribe(() => { + this.updateValidators(); + }); + } + + registerOnChange(fn: any): void { + this.propagateChange = fn; + } + + registerOnTouched(_fn: any): void { + } + + setDisabledState(isDisabled: boolean): void { + this.disabled = isDisabled; + if (isDisabled) { + this.tripTimelineSettingsFormGroup.disable({emitEvent: false}); + } else { + this.tripTimelineSettingsFormGroup.enable({emitEvent: false}); + this.updateValidators(); + } + } + + writeValue(value: TripTimelineSettings): void { + this.modelValue = value; + this.tripTimelineSettingsFormGroup.patchValue( + value, {emitEvent: false} + ); + this.updateValidators(); + this.settingsExpanded = this.tripTimelineSettingsFormGroup.get('showTimelineControl').value; + this.tripTimelineSettingsFormGroup.get('showTimelineControl').valueChanges.pipe( + takeUntilDestroyed(this.destroyRef) + ).subscribe((show) => { + this.settingsExpanded = show; + }); + } + + public validate(c: UntypedFormControl) { + const valid = this.tripTimelineSettingsFormGroup.valid; + return valid ? null : { + tripTimelineSettings: { + valid: false, + }, + }; + } + + private updateValidators() { + const showTimelineControl: boolean = this.tripTimelineSettingsFormGroup.get('showTimelineControl').value; + const showTimestamp: boolean = this.tripTimelineSettingsFormGroup.get('showTimestamp').value; + const snapToRealLocation: boolean = this.tripTimelineSettingsFormGroup.get('snapToRealLocation').value; + if (showTimelineControl) { + this.tripTimelineSettingsFormGroup.enable({emitEvent: false}); + if (!showTimestamp) { + this.tripTimelineSettingsFormGroup.get('timestampFormat').disable({emitEvent: false}); + } + if (!snapToRealLocation) { + this.tripTimelineSettingsFormGroup.get('locationSnapFilter').disable({emitEvent: false}); + } + } else { + this.tripTimelineSettingsFormGroup.disable({emitEvent: false}); + this.tripTimelineSettingsFormGroup.get('showTimelineControl').enable({emitEvent: false}); + } + } + + private updateModel() { + this.modelValue = this.tripTimelineSettingsFormGroup.getRawValue(); + this.propagateChange(this.modelValue); + } + + protected readonly pathDecoratorSymbols = pathDecoratorSymbols; + protected readonly pathDecoratorSymbolTranslationMap = pathDecoratorSymbolTranslationMap; +} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/widget-settings-common.module.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/widget-settings-common.module.ts index 536623e86b..ef7dca9a11 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/widget-settings-common.module.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/widget-settings-common.module.ts @@ -242,6 +242,9 @@ import { import { MapActionButtonRowComponent } from '@home/components/widget/lib/settings/common/map/map-action-button-row.component'; +import { + TripTimelineSettingsComponent +} from '@home/components/widget/lib/settings/common/map/trip-timeline-settings.component'; @NgModule({ declarations: [ @@ -332,6 +335,7 @@ import { MapDataLayersComponent, MapDataSourceRowComponent, MapDataSourcesComponent, + TripTimelineSettingsComponent, MapSettingsComponent, EntityAliasSelectComponent, FilterSelectComponent, diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/map/map-widget-settings.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/map/map-widget-settings.component.html index 870c1f6deb..f58e1d65ab 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/map/map-widget-settings.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/map/map-widget-settings.component.html @@ -17,6 +17,7 @@ --> , private fb: UntypedFormBuilder) { super(store); @@ -40,6 +43,13 @@ export class MapWidgetSettingsComponent extends WidgetSettingsComponent { return this.mapWidgetSettingsForm; } + protected onWidgetConfigSet(widgetConfig: WidgetConfigComponentData) { + const params = widgetConfig.typeParameters as any; + if (isDefinedAndNotNull(params.trip)) { + this.trip = params.trip === true; + } + } + protected defaultSettings(): WidgetSettings { return mergeDeepIgnoreArray({} as MapWidgetSettings, mapWidgetDefaultSettings); } diff --git a/ui-ngx/src/assets/locale/locale.constant-en_US.json b/ui-ngx/src/assets/locale/locale.constant-en_US.json index cc6acce2b2..39156e001b 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -7706,6 +7706,14 @@ "zoom-double-click": "Double click", "zoom-control-buttons": "Control buttons" }, + "timeline": { + "control-panel": "Timeline control panel", + "time-step": "Time step", + "speed-options": "Speed options", + "timestamp": "Timestamp", + "snap-to-real-location": "Snap to real location", + "location-snap-filter-function": "Location snap filter function" + }, "map-action": { "map-action-buttons": "Map action buttons", "label": "Label", @@ -7779,6 +7787,7 @@ }, "overlays": { "overlays": "Overlays", + "trips": "Trips", "markers": "Markers", "polygons": "Polygons", "circles": "Circles" @@ -7827,7 +7836,14 @@ "action-remove": "Remove", "edit-instruments": "Instruments", "enable-snapping": "Enable snapping to other vertices for precision drawing", + "trip": { + "no-trips": "No trips configured", + "add-trip": "Add trip", + "trip-configuration": "Trip configuration", + "remove-trip": "Remove trip" + }, "marker": { + "marker": "Marker", "latitude-key": "Latitude key", "longitude-key": "Longitude key", "x-pos-key": "X position key", @@ -7857,6 +7873,8 @@ "marker-offset": "Marker offset", "offset-horizontal": "Horizontal", "offset-vertical": "Vertical", + "rotate-marker": "Rotate marker", + "offset-angle": "Offset angle", "position-conversion": "Position conversion", "position-conversion-function": "Position conversion function, should return x,y coordinates as double from 0 to 1 each", "clustering": { @@ -7880,8 +7898,19 @@ "place-marker-hint-with-entity": "Click to place '{{entityName}}' entity" }, "path": { + "path": "Path", + "path-decorator": "Path decorator", + "decorator-symbol": "Decorator symbol", "decorator-symbol-arrow-head": "Arrow", - "decorator-symbol-dash": "Dash" + "decorator-symbol-dash": "Dash", + "decorator-arrangement": "Decorator arrangement", + "decorator-offset": "Start", + "decorator-end-offset": "End", + "decorator-repeat": "Repeat" + }, + "points": { + "points": "Points", + "point-tooltip": "Point tooltip" }, "polygon": { "polygon-key": "Polygon key", diff --git a/ui-ngx/src/assets/markers/tripShape1.svg b/ui-ngx/src/assets/markers/tripShape1.svg new file mode 100644 index 0000000000..da98975b52 --- /dev/null +++ b/ui-ngx/src/assets/markers/tripShape1.svg @@ -0,0 +1,4 @@ + + + + diff --git a/ui-ngx/src/assets/markers/tripShape10.svg b/ui-ngx/src/assets/markers/tripShape10.svg new file mode 100644 index 0000000000..507d3725de --- /dev/null +++ b/ui-ngx/src/assets/markers/tripShape10.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/ui-ngx/src/assets/markers/tripShape2.svg b/ui-ngx/src/assets/markers/tripShape2.svg new file mode 100644 index 0000000000..0dfc4e22a1 --- /dev/null +++ b/ui-ngx/src/assets/markers/tripShape2.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + diff --git a/ui-ngx/src/assets/markers/tripShape3.svg b/ui-ngx/src/assets/markers/tripShape3.svg new file mode 100644 index 0000000000..e49d699ad2 --- /dev/null +++ b/ui-ngx/src/assets/markers/tripShape3.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/ui-ngx/src/assets/markers/tripShape4.svg b/ui-ngx/src/assets/markers/tripShape4.svg new file mode 100644 index 0000000000..f4b48da113 --- /dev/null +++ b/ui-ngx/src/assets/markers/tripShape4.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/ui-ngx/src/assets/markers/tripShape5.svg b/ui-ngx/src/assets/markers/tripShape5.svg new file mode 100644 index 0000000000..a88d26035d --- /dev/null +++ b/ui-ngx/src/assets/markers/tripShape5.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/ui-ngx/src/assets/markers/tripShape6.svg b/ui-ngx/src/assets/markers/tripShape6.svg new file mode 100644 index 0000000000..23105baba5 --- /dev/null +++ b/ui-ngx/src/assets/markers/tripShape6.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/ui-ngx/src/assets/markers/tripShape7.svg b/ui-ngx/src/assets/markers/tripShape7.svg new file mode 100644 index 0000000000..342c9db364 --- /dev/null +++ b/ui-ngx/src/assets/markers/tripShape7.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/ui-ngx/src/assets/markers/tripShape8.svg b/ui-ngx/src/assets/markers/tripShape8.svg new file mode 100644 index 0000000000..f8edbe4dc0 --- /dev/null +++ b/ui-ngx/src/assets/markers/tripShape8.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/ui-ngx/src/assets/markers/tripShape9.svg b/ui-ngx/src/assets/markers/tripShape9.svg new file mode 100644 index 0000000000..9494a976f0 --- /dev/null +++ b/ui-ngx/src/assets/markers/tripShape9.svg @@ -0,0 +1,10 @@ + + + + + + + + + +