diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/chart/bar-chart-with-labels-widget.component.scss b/ui-ngx/src/app/modules/home/components/widget/lib/chart/bar-chart-with-labels-widget.component.scss
index 3c584b7fb8..76cae3cf24 100644
--- a/ui-ngx/src/app/modules/home/components/widget/lib/chart/bar-chart-with-labels-widget.component.scss
+++ b/ui-ngx/src/app/modules/home/components/widget/lib/chart/bar-chart-with-labels-widget.component.scss
@@ -104,4 +104,11 @@
}
}
}
+
+ .tb-widget-title-row .tb-widget-actions {
+ margin: 0;
+ }
+ .tb-widget-actions.tb-widget-actions-absolute {
+ z-index: 2;
+ }
}
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/chart/bar-chart-with-labels-widget.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/chart/bar-chart-with-labels-widget.component.ts
index 51be4a70bf..746daf43b3 100644
--- a/ui-ngx/src/app/modules/home/components/widget/lib/chart/bar-chart-with-labels-widget.component.ts
+++ b/ui-ngx/src/app/modules/home/components/widget/lib/chart/bar-chart-with-labels-widget.component.ts
@@ -40,6 +40,7 @@ import {
} from '@home/components/widget/lib/chart/bar-chart-with-labels-widget.models';
import { TbTimeSeriesChart } from '@home/components/widget/lib/chart/time-series-chart';
import { DataKey } from '@shared/models/widget.models';
+import { WidgetComponent } from '@home/components/widget/widget.component';
@Component({
selector: 'tb-bar-chart-with-labels-widget',
@@ -57,9 +58,6 @@ export class BarChartWithLabelsWidgetComponent implements OnInit, OnDestroy, Aft
@Input()
ctx: WidgetContext;
- @Input()
- widgetTitlePanel: TemplateRef
;
-
showLegend: boolean;
legendClass: string;
@@ -73,7 +71,8 @@ export class BarChartWithLabelsWidgetComponent implements OnInit, OnDestroy, Aft
private timeSeriesChart: TbTimeSeriesChart;
- constructor(private imagePipe: ImagePipe,
+ constructor(public widgetComponent: WidgetComponent,
+ private imagePipe: ImagePipe,
private sanitizer: DomSanitizer,
private renderer: Renderer2,
private cd: ChangeDetectorRef) {
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/chart/latest-chart-widget.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/chart/latest-chart-widget.component.html
index 7f7fb47389..8e90ab8fb6 100644
--- a/ui-ngx/src/app/modules/home/components/widget/lib/chart/latest-chart-widget.component.html
+++ b/ui-ngx/src/app/modules/home/components/widget/lib/chart/latest-chart-widget.component.html
@@ -19,6 +19,5 @@
#latestChart
[ctx]="ctx"
[settings]="settings"
- [widgetTitlePanel]="widgetTitlePanel"
[callbacks]="callbacks"
>
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/chart/latest-chart.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/chart/latest-chart.component.html
index 578f7fdf4b..20cfdf4623 100644
--- a/ui-ngx/src/app/modules/home/components/widget/lib/chart/latest-chart.component.html
+++ b/ui-ngx/src/app/modules/home/components/widget/lib/chart/latest-chart.component.html
@@ -17,7 +17,14 @@
-->
-
+ @if (widgetComponent.dashboardWidget.showWidgetTitlePanel) {
+
+
+
+
+ } @else {
+
+ }
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/chart/latest-chart.component.scss b/ui-ngx/src/app/modules/home/components/widget/lib/chart/latest-chart.component.scss
index 3566820c8d..2eb0ceb940 100644
--- a/ui-ngx/src/app/modules/home/components/widget/lib/chart/latest-chart.component.scss
+++ b/ui-ngx/src/app/modules/home/components/widget/lib/chart/latest-chart.component.scss
@@ -106,4 +106,11 @@
}
}
}
+
+ .tb-widget-title-row .tb-widget-actions {
+ margin: 0;
+ }
+ .tb-widget-actions.tb-widget-actions-absolute {
+ z-index: 2;
+ }
}
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/chart/latest-chart.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/chart/latest-chart.component.ts
index 651240342c..85905dfd3e 100644
--- a/ui-ngx/src/app/modules/home/components/widget/lib/chart/latest-chart.component.ts
+++ b/ui-ngx/src/app/modules/home/components/widget/lib/chart/latest-chart.component.ts
@@ -68,9 +68,6 @@ export class LatestChartComponent implements OnInit, OnDestroy, AfterViewInit {
@Input()
ctx: WidgetContext;
- @Input()
- widgetTitlePanel: TemplateRef
;
-
@Input()
callbacks: LatestChartComponentCallbacks;
@@ -98,9 +95,9 @@ export class LatestChartComponent implements OnInit, OnDestroy, AfterViewInit {
private latestChart: TbLatestChart;
- constructor(private imagePipe: ImagePipe,
+ constructor(public widgetComponent: WidgetComponent,
+ private imagePipe: ImagePipe,
private sanitizer: DomSanitizer,
- private widgetComponent: WidgetComponent,
private renderer: Renderer2,
private translate: TranslateService,
private cd: ChangeDetectorRef) {
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/chart/range-chart-widget.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/chart/range-chart-widget.component.html
index 8880503eb5..39112c484b 100644
--- a/ui-ngx/src/app/modules/home/components/widget/lib/chart/range-chart-widget.component.html
+++ b/ui-ngx/src/app/modules/home/components/widget/lib/chart/range-chart-widget.component.html
@@ -17,7 +17,14 @@
-->
-
+ @if (widgetComponent.dashboardWidget.showWidgetTitlePanel) {
+
+
+
+
+ } @else {
+
+ }
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/chart/range-chart-widget.component.scss b/ui-ngx/src/app/modules/home/components/widget/lib/chart/range-chart-widget.component.scss
index 6f4b21a78d..ff339362a1 100644
--- a/ui-ngx/src/app/modules/home/components/widget/lib/chart/range-chart-widget.component.scss
+++ b/ui-ngx/src/app/modules/home/components/widget/lib/chart/range-chart-widget.component.scss
@@ -104,4 +104,10 @@
}
}
}
+ .tb-widget-title-row .tb-widget-actions {
+ margin: 0;
+ }
+ .tb-widget-actions.tb-widget-actions-absolute {
+ z-index: 2;
+ }
}
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/chart/range-chart-widget.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/chart/range-chart-widget.component.ts
index eacbb883a4..0201ec7d5f 100644
--- a/ui-ngx/src/app/modules/home/components/widget/lib/chart/range-chart-widget.component.ts
+++ b/ui-ngx/src/app/modules/home/components/widget/lib/chart/range-chart-widget.component.ts
@@ -48,6 +48,7 @@ import { Observable } from 'rxjs';
import { ImagePipe } from '@shared/pipe/image.pipe';
import { DomSanitizer } from '@angular/platform-browser';
import { TbTimeSeriesChart } from '@home/components/widget/lib/chart/time-series-chart';
+import { WidgetComponent } from '@home/components/widget/widget.component';
@Component({
selector: 'tb-range-chart-widget',
@@ -65,9 +66,6 @@ export class RangeChartWidgetComponent implements OnInit, OnDestroy, AfterViewIn
@Input()
ctx: WidgetContext;
- @Input()
- widgetTitlePanel: TemplateRef
;
-
showLegend: boolean;
legendClass: string;
@@ -86,7 +84,8 @@ export class RangeChartWidgetComponent implements OnInit, OnDestroy, AfterViewIn
private timeSeriesChart: TbTimeSeriesChart;
- constructor(private imagePipe: ImagePipe,
+ constructor(public widgetComponent: WidgetComponent,
+ private imagePipe: ImagePipe,
private sanitizer: DomSanitizer,
private renderer: Renderer2,
private cd: ChangeDetectorRef) {
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart-widget.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart-widget.component.html
index 1ba501674e..1efcf30781 100644
--- a/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart-widget.component.html
+++ b/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart-widget.component.html
@@ -17,9 +17,14 @@
-->
-
-
-
+ @if (widgetComponent.dashboardWidget.showWidgetTitlePanel) {
+
+
+
+
+ } @else {
+
+ }
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart-widget.component.scss b/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart-widget.component.scss
index f500d3009b..37067ef935 100644
--- a/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart-widget.component.scss
+++ b/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart-widget.component.scss
@@ -156,4 +156,10 @@ $maxLegendHeight: 35%;
}
}
}
+ .tb-widget-title-row .tb-widget-actions {
+ margin: 0;
+ }
+ .tb-widget-actions.tb-widget-actions-absolute {
+ z-index: 2;
+ }
}
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart-widget.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart-widget.component.ts
index 2e9f297b4a..2fd12b1b98 100644
--- a/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart-widget.component.ts
+++ b/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart-widget.component.ts
@@ -61,9 +61,6 @@ export class TimeSeriesChartWidgetComponent implements OnInit, OnDestroy, AfterV
@Input()
ctx: WidgetContext;
- @Input()
- widgetTitlePanel: TemplateRef
;
-
horizontalLegendPosition = false;
showLegend: boolean;
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart.ts b/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart.ts
index 2b4b52ccfb..7bea76d325 100644
--- a/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart.ts
+++ b/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart.ts
@@ -793,7 +793,7 @@ export class TbTimeSeriesChart {
}
} else {
if (!axis.option.name) {
- axis.option.name = axis.settings.label;
+ axis.option.name = this.ctx.utilsService.customTranslation(axis.settings.label, axis.settings.label);
result.changed = true;
}
const nameGap = size;
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/maps/data-layer/circles-data-layer.ts b/ui-ngx/src/app/modules/home/components/widget/lib/maps/data-layer/circles-data-layer.ts
index 9e25ddb486..2d3fc6b2fe 100644
--- a/ui-ngx/src/app/modules/home/components/widget/lib/maps/data-layer/circles-data-layer.ts
+++ b/ui-ngx/src/app/modules/home/components/widget/lib/maps/data-layer/circles-data-layer.ts
@@ -22,7 +22,7 @@ import {
TbMapDatasource
} from '@shared/models/widget/maps/map.models';
import L from 'leaflet';
-import { FormattedData } from '@shared/models/widget.models';
+import { DataKey, FormattedData } from '@shared/models/widget.models';
import { TbShapesDataLayer } from '@home/components/widget/lib/maps/data-layer/shapes-data-layer';
import { TbMap } from '@home/components/widget/lib/maps/map';
import { Observable } from 'rxjs';
@@ -213,9 +213,8 @@ export class TbCirclesDataLayer extends TbShapesDataLayer): Partial {
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/maps/data-layer/map-data-layer.ts b/ui-ngx/src/app/modules/home/components/widget/lib/maps/data-layer/map-data-layer.ts
index 63d77c48b2..5dcb80ec41 100644
--- a/ui-ngx/src/app/modules/home/components/widget/lib/maps/data-layer/map-data-layer.ts
+++ b/ui-ngx/src/app/modules/home/components/widget/lib/maps/data-layer/map-data-layer.ts
@@ -41,7 +41,7 @@ import L from 'leaflet';
import { CompiledTbFunction } from '@shared/models/js-function.models';
import { forkJoin, Observable, of } from 'rxjs';
import { map } from 'rxjs/operators';
-import { DataKey, FormattedData } from '@shared/models/widget.models';
+import { DataKey, DatasourceType, FormattedData } from '@shared/models/widget.models';
import { CustomTranslatePipe } from '@shared/pipe/custom-translate.pipe';
import { TbMap } from '@home/components/widget/lib/maps/map';
import { WidgetContext } from '@home/models/widget-component.models';
@@ -170,7 +170,7 @@ export abstract class TbMapDataLayer {
- this.datasource = mapDataSourceSettingsToDatasource(this.settings);
- this.datasource.dataKeys = this.settings.additionalDataKeys ? [...this.settings.additionalDataKeys] : [];
- const colorRangeKeys = this.allColorSettings().filter(settings => settings.type === DataLayerColorType.range && settings.rangeKey)
- .map(settings => settings.rangeKey);
- this.datasource.dataKeys.push(...colorRangeKeys);
- this.mapDataId = this.datasource.mapDataIds[0];
- this.datasource = this.setupDatasource(this.datasource);
+ this.dataSources = [];
+ const datasource = mapDataSourceSettingsToDatasource(this.settings);
+ this.mapDataId = datasource.mapDataIds[0];
+ this.dataSources.push(datasource);
+ if (this.settings.additionalDataSources?.length && datasource.type !== DatasourceType.function) {
+ this.dataSources.push(...this.settings.additionalDataSources.map(ds => mapDataSourceSettingsToDatasource(ds, this.mapDataId)));
+ }
+ const dataKeys = this.calculateDataKeys();
+ const latestDataKeys = this.calculateLatestDataKeys();
+ this.dataSources.forEach(ds => ds.dataKeys.push(...dataKeys));
+ if (latestDataKeys?.length) {
+ this.dataSources.forEach(ds => ds.latestDataKeys.push(...latestDataKeys));
+ }
return forkJoin(
[
this.dataLayerLabelProcessor ? this.dataLayerLabelProcessor.setup() : of(null),
@@ -241,8 +247,8 @@ export abstract class TbMapDataLayer settings.type === DataLayerColorType.range && settings.rangeKey)
+ .map(settings => settings.rangeKey);
+ dataKeys.push(...colorRangeKeys);
+ dataKeys.push(...this.getDataKeys());
+ return dataKeys;
+ }
+
+ protected calculateLatestDataKeys(): DataKey[] {
+ return [];
+ }
+
+ protected getDataKeys(): DataKey[] {
+ return [];
}
protected allColorSettings(): DataLayerColorSettings[] {
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/maps/data-layer/markers-data-layer.ts b/ui-ngx/src/app/modules/home/components/widget/lib/maps/data-layer/markers-data-layer.ts
index 3696435683..093cc265bb 100644
--- a/ui-ngx/src/app/modules/home/components/widget/lib/maps/data-layer/markers-data-layer.ts
+++ b/ui-ngx/src/app/modules/home/components/widget/lib/maps/data-layer/markers-data-layer.ts
@@ -37,7 +37,7 @@ import {
TbMapDatasource
} from '@shared/models/widget/maps/map.models';
import L, { FeatureGroup } from 'leaflet';
-import { FormattedData } from '@shared/models/widget.models';
+import { DataKey, FormattedData } from '@shared/models/widget.models';
import { forkJoin, Observable, of } from 'rxjs';
import { CompiledTbFunction } from '@shared/models/js-function.models';
import {
@@ -628,9 +628,8 @@ export class TbMarkersDataLayer extends TbLatestMapDataLayer): Partial {
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 e598685786..ae2f62853e 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
@@ -351,10 +351,10 @@ class TbTripDataItem extends TbDataLayerItem 1) {
+ result[timeStamp[timeStamp.length - 1]].rotationAngle = result[timeStamp[timeStamp.length - 2]].rotationAngle;
+ }
+ } else if (this.settings.rotateMarker && timeStamp.length > 1) {
+ const startPoint = this.dataLayer.dataProcessor.extractLocation(result[timeStamp[timeStamp.length - 2]], dsData);
+ const endPoint = this.dataLayer.dataProcessor.extractLocation(result[timeStamp[timeStamp.length - 1]], dsData);
+ result[timeStamp[timeStamp.length - 1]].rotationAngle += findRotationAngle(startPoint, endPoint);
}
return result;
}
@@ -528,22 +535,33 @@ export class TbTripsDataLayer extends TbMapDataLayer settings.type === DataLayerColorType.range && settings.rangeKey)
- .map(settings => settings.rangeKey);
+ .map(settings => settings.rangeKey);
if (this.settings.additionalDataKeys?.length) {
additionalKeys.push(...this.settings.additionalDataKeys);
}
if (additionalKeys.length) {
const tsKeys = additionalKeys.filter(key => key.type === DataKeyType.timeseries);
+ dataKeys.push(...tsKeys);
+ }
+ return dataKeys;
+ }
+
+ protected calculateLatestDataKeys(): DataKey[] {
+ const additionalKeys = this.allColorSettings().filter(settings => settings.type === DataLayerColorType.range && settings.rangeKey)
+ .map(settings => settings.rangeKey);
+ if (this.settings.additionalDataKeys?.length) {
+ additionalKeys.push(...this.settings.additionalDataKeys);
+ }
+ if (additionalKeys.length) {
const latestKeys = additionalKeys.filter(key => key.type !== DataKeyType.timeseries);
- datasource.dataKeys.push(...tsKeys);
if (latestKeys.length) {
- datasource.latestDataKeys = latestKeys;
+ return latestKeys;
}
}
- return datasource;
+ return [];
}
protected allColorSettings(): DataLayerColorSettings[] {
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/maps/map-widget.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/maps/map-widget.component.html
index b45869addf..e64ee71170 100644
--- a/ui-ngx/src/app/modules/home/components/widget/lib/maps/map-widget.component.html
+++ b/ui-ngx/src/app/modules/home/components/widget/lib/maps/map-widget.component.html
@@ -17,9 +17,14 @@
-->
-
-
-
+ @if (widgetComponent.dashboardWidget.showWidgetTitlePanel) {
+
+
+
+
+ } @else {
+
+ }
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/maps/map-widget.component.scss b/ui-ngx/src/app/modules/home/components/widget/lib/maps/map-widget.component.scss
index 88157426f3..eab92a664f 100644
--- a/ui-ngx/src/app/modules/home/components/widget/lib/maps/map-widget.component.scss
+++ b/ui-ngx/src/app/modules/home/components/widget/lib/maps/map-widget.component.scss
@@ -34,4 +34,10 @@
div.tb-widget-title {
padding: 0;
}
+ .tb-widget-title-row .tb-widget-actions {
+ margin: 0;
+ }
+ .tb-widget-actions.tb-widget-actions-absolute {
+ z-index: 2;
+ }
}
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/maps/map-widget.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/maps/map-widget.component.ts
index 013a6c88a9..4ef3c3cd45 100644
--- a/ui-ngx/src/app/modules/home/components/widget/lib/maps/map-widget.component.ts
+++ b/ui-ngx/src/app/modules/home/components/widget/lib/maps/map-widget.component.ts
@@ -21,7 +21,6 @@ import {
Input,
OnDestroy,
OnInit,
- TemplateRef,
ViewChild,
ViewEncapsulation
} from '@angular/core';
@@ -55,9 +54,6 @@ export class MapWidgetComponent implements OnInit, OnDestroy {
@Input()
ctx: WidgetContext;
- @Input()
- widgetTitlePanel: TemplateRef;
-
backgroundStyle$: Observable;
overlayStyle: ComponentStyle = {};
padding: string;
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/maps/map.ts b/ui-ngx/src/app/modules/home/components/widget/lib/maps/map.ts
index 72f075e8f1..f1bb414878 100644
--- a/ui-ngx/src/app/modules/home/components/widget/lib/maps/map.ts
+++ b/ui-ngx/src/app/modules/home/components/widget/lib/maps/map.ts
@@ -308,7 +308,8 @@ export abstract class TbMap {
() => {
let datasources: TbMapDatasource[];
for (const layerType of mapDataLayerTypes) {
- const typeDatasources = this.latestDataLayers.filter(dl => dl.dataLayerType() === layerType).map(dl => dl.getDatasource());
+ const typeDatasources = this.latestDataLayers.filter(dl => dl.dataLayerType() === layerType)
+ .map(dl => dl.getDataSources()).flat();
if (!datasources) {
datasources = typeDatasources;
} else {
@@ -349,7 +350,7 @@ export abstract class TbMap {
);
}
if (this.tripDataLayers.length) {
- const tripDatasources = this.tripDataLayers.map(dl => dl.getDatasource());
+ const tripDatasources = this.tripDataLayers.map(dl => dl.getDataSources()).flat();
const tripDataLayersSubscriptionOptions: WidgetSubscriptionOptions = {
datasources: tripDatasources,
hasDataPageLink: true,
@@ -926,7 +927,7 @@ export abstract class TbMap {
private calculateCurrentTime(minTime: number, maxTime: number): number {
if (minTime !== this.minTime || maxTime !== this.maxTime) {
- if (this.minTime >= this.currentTime || isUndefined(this.currentTime)) {
+ if (this.minTime >= this.currentTime || isUndefined(this.currentTime) || this.currentTime === Infinity) {
return this.minTime;
} else if (this.maxTime <= this.currentTime) {
return this.maxTime;
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 7044ffa5fe..f9d0fa6d07 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
@@ -202,7 +202,10 @@ export class MapTimelinePanelComponent implements OnInit, OnDestroy {
public moveNext() {
if (this.index < this.maxTimeIndex) {
if (this.settings.snapToRealLocation) {
- const anchorIndex = this.findIndex(this.currentTime, this.anchors) + 1;
+ let anchorIndex = this.findIndex(this.currentTime, this.anchors) + 1;
+ if (anchorIndex >= this.anchors.length) {
+ anchorIndex = this.anchors.length - 1;
+ }
this.index = Math.floor((this.anchors[anchorIndex] - this.minValue) / this.settings.timeStep);
} else {
this.index++;
@@ -214,7 +217,10 @@ export class MapTimelinePanelComponent implements OnInit, OnDestroy {
public movePrev() {
if (this.index > this.minTimeIndex) {
if (this.settings.snapToRealLocation) {
- const anchorIndex = this.findIndex(this.currentTime, this.anchors) - 1;
+ let anchorIndex = this.findIndex(this.currentTime, this.anchors) - 1;
+ if (anchorIndex < 0) {
+ anchorIndex = 0;
+ }
this.index = Math.floor((this.anchors[anchorIndex] - this.minValue) / this.settings.timeStep);
} else {
this.index--;
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/time-series-chart-widget-settings.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/time-series-chart-widget-settings.component.ts
index dc56c2a328..68d849fd0c 100644
--- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/time-series-chart-widget-settings.component.ts
+++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/time-series-chart-widget-settings.component.ts
@@ -105,6 +105,17 @@ export class TimeSeriesChartWidgetSettingsComponent extends WidgetSettingsCompon
const params = widgetConfig.typeParameters as any;
if (isDefinedAndNotNull(params.chartType)) {
this.chartType = params.chartType;
+ } else {
+ this.chartType = TimeSeriesChartType.default;
+ }
+ if (this.timeSeriesChartWidgetSettingsForm) {
+ const isStateChartType = this.chartType === TimeSeriesChartType.state;
+ const hasStatesControl = this.timeSeriesChartWidgetSettingsForm.contains('states');
+ if (isStateChartType && !hasStatesControl) {
+ this.timeSeriesChartWidgetSettingsForm.addControl('states', this.fb.control(widgetConfig.config.settings.states), { emitEvent: false });
+ } else if (!isStateChartType && hasStatesControl) {
+ this.timeSeriesChartWidgetSettingsForm.removeControl('states', { emitEvent: false });
+ }
}
}
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/action/widget-action.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/action/widget-action.component.html
index a2a0e8c8fa..5c735010f1 100644
--- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/action/widget-action.component.html
+++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/action/widget-action.component.html
@@ -37,7 +37,7 @@
widget-config.action
-
+
{{ widgetActionTypeTranslations.get(widgetActionType[actionType]) | translate }}
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/action/widget-action.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/action/widget-action.component.ts
index bd0fe36910..698645bacf 100644
--- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/action/widget-action.component.ts
+++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/action/widget-action.component.ts
@@ -26,7 +26,7 @@ import {
ValidatorFn,
Validators
} from '@angular/forms';
-import { Component, ElementRef, forwardRef, Input, OnInit, ViewChild } from '@angular/core';
+import { Component, computed, ElementRef, forwardRef, input, Input, OnInit, ViewChild } from '@angular/core';
import {
MapItemType,
mapItemTypeTranslationMap,
@@ -102,19 +102,16 @@ export class WidgetActionComponent implements ControlValueAccessor, OnInit, Vali
@Input()
actionNames: string[];
- @Input()
- set additionalWidgetActionTypes(value: WidgetActionType[]) {
- if (this.widgetActionFormGroup && !widgetActionTypes.includes(this.widgetActionFormGroup.get('type').value)) {
- this.widgetActionFormGroup.get('type').setValue(WidgetActionType.doNothing);
- }
- if (value?.length) {
- this.widgetActionTypes = widgetActionTypes.concat(value);
- } else {
- this.widgetActionTypes = widgetActionTypes;
+ additionalWidgetActionTypes = input(null);
+
+ actionTypes = computed(() => {
+ const predefinedActionTypes = widgetActionTypes;
+ if (this.additionalWidgetActionTypes()?.length) {
+ return predefinedActionTypes.concat(this.additionalWidgetActionTypes());
}
- }
+ return predefinedActionTypes;
+ });
- widgetActionTypes = widgetActionTypes;
widgetActionTypeTranslations = widgetActionTypeTranslationMap;
widgetActionType = WidgetActionType;
@@ -191,9 +188,6 @@ export class WidgetActionComponent implements ControlValueAccessor, OnInit, Vali
).subscribe(() => {
this.widgetActionUpdated();
});
- if (this.additionalWidgetActionTypes) {
- this.widgetActionTypes = this.widgetActionTypes.concat(this.additionalWidgetActionTypes);
- }
}
writeValue(widgetAction?: WidgetAction): void {
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/filter/filter-select.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/filter/filter-select.component.html
index 5e67638092..ff1bcb06a4 100644
--- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/filter/filter-select.component.html
+++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/filter/filter-select.component.html
@@ -15,9 +15,14 @@
limitations under the License.
-->
-
- {{ 'filter.filter' | translate }}
-
+ {{ 'filter.filter' | translate }}
+
-
-
+
-
+
{{ 'filter.filter-required' | translate }}
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/filter/filter-select.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/filter/filter-select.component.ts
index 5653cb04c0..6484f2ca18 100644
--- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/filter/filter-select.component.ts
+++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/filter/filter-select.component.ts
@@ -70,6 +70,10 @@ export class FilterSelectComponent implements ControlValueAccessor, OnInit, Afte
@coerceBoolean()
showLabel: boolean;
+ @Input()
+ @coerceBoolean()
+ inlineField: boolean;
+
@Input()
appearance: MatFormFieldAppearance = 'fill';
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/additional-map-data-source-row.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/additional-map-data-source-row.component.html
new file mode 100644
index 0000000000..8f5094741c
--- /dev/null
+++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/additional-map-data-source-row.component.html
@@ -0,0 +1,80 @@
+
+
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/additional-map-data-source-row.component.scss b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/additional-map-data-source-row.component.scss
new file mode 100644
index 0000000000..3d5db8d73a
--- /dev/null
+++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/additional-map-data-source-row.component.scss
@@ -0,0 +1,56 @@
+/**
+ * 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 '../../../../../../../../../scss/constants';
+
+.tb-form-table-row.tb-additional-map-data-source-row {
+
+ .tb-source-field {
+ flex: 1 1 50%;
+ display: flex;
+ gap: 12px;
+ .tb-ds-type-field, .tb-label-field, .tb-device-field, .tb-entity-alias-field {
+ flex: 1;
+ }
+ }
+
+ .tb-data-keys-field {
+ flex: 1 1 50%;
+ min-width: 0;
+ }
+
+ .tb-remove-button {
+ width: 40px;
+ min-width: 40px;
+ }
+
+ @media #{$mat-lt-lg} {
+ .tb-source-field {
+ flex-direction: column;
+ flex: 1 1 30%;
+ }
+ .tb-data-keys-field {
+ flex: 1 1 70%;
+ }
+ @media #{$mat-lt-md} {
+ .tb-source-field {
+ flex: 1 1 50%;
+ }
+ .tb-data-keys-field {
+ flex: 1 1 50%;
+ }
+ }
+ }
+}
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/additional-map-data-source-row.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/additional-map-data-source-row.component.ts
new file mode 100644
index 0000000000..0957d991f5
--- /dev/null
+++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/additional-map-data-source-row.component.ts
@@ -0,0 +1,207 @@
+///
+/// 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 {
+ ChangeDetectorRef,
+ Component,
+ DestroyRef,
+ EventEmitter,
+ forwardRef,
+ Input,
+ OnInit,
+ Output,
+ ViewEncapsulation
+} from '@angular/core';
+import {
+ ControlValueAccessor,
+ NG_VALUE_ACCESSOR,
+ UntypedFormBuilder,
+ UntypedFormGroup,
+ Validators
+} from '@angular/forms';
+import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
+import { AdditionalMapDataSourceSettings } from '@shared/models/widget/maps/map.models';
+import { DataKey, DatasourceType, datasourceTypeTranslationMap, widgetType } from '@shared/models/widget.models';
+import { EntityType } from '@shared/models/entity-type.models';
+import { DataKeyType } from '@shared/models/telemetry/telemetry.models';
+import { genNextLabelForDataKeys } from '@core/utils';
+import { MapSettingsContext } from '@home/components/widget/lib/settings/common/map/map-settings.component.models';
+
+@Component({
+ selector: 'tb-additional-map-data-source-row',
+ templateUrl: './additional-map-data-source-row.component.html',
+ styleUrls: ['./additional-map-data-source-row.component.scss'],
+ providers: [
+ {
+ provide: NG_VALUE_ACCESSOR,
+ useExisting: forwardRef(() => AdditionalMapDataSourceRowComponent),
+ multi: true
+ }
+ ],
+ encapsulation: ViewEncapsulation.None
+})
+export class AdditionalMapDataSourceRowComponent implements ControlValueAccessor, OnInit {
+
+ DatasourceType = DatasourceType;
+ DataKeyType = DataKeyType;
+
+ EntityType = EntityType;
+
+ widgetType = widgetType;
+
+ datasourceTypes: Array = [];
+ datasourceTypesTranslations = datasourceTypeTranslationMap;
+
+ @Input()
+ disabled: boolean;
+
+ @Input()
+ context: MapSettingsContext;
+
+ @Output()
+ dataSourceRemoved = new EventEmitter();
+
+ dataSourceFormGroup: UntypedFormGroup;
+
+ generateAdditionalDataKey = this.generateDataKey.bind(this);
+
+ modelValue: AdditionalMapDataSourceSettings;
+
+ private propagateChange = (_val: any) => {};
+
+ constructor(private fb: UntypedFormBuilder,
+ private cd: ChangeDetectorRef,
+ private destroyRef: DestroyRef) {
+ }
+
+ ngOnInit() {
+ if (this.context.functionsOnly) {
+ this.datasourceTypes = [DatasourceType.function];
+ } else {
+ this.datasourceTypes = [DatasourceType.function, DatasourceType.device, DatasourceType.entity];
+ }
+ this.dataSourceFormGroup = this.fb.group({
+ dsType: [null, [Validators.required]],
+ dsLabel: [null, []],
+ dsDeviceId: [null, [Validators.required]],
+ dsEntityAliasId: [null, [Validators.required]],
+ dataKeys: [null, [Validators.required]]
+ });
+ this.dataSourceFormGroup.valueChanges.pipe(
+ takeUntilDestroyed(this.destroyRef)
+ ).subscribe(
+ () => this.updateModel()
+ );
+ this.dataSourceFormGroup.get('dsType').valueChanges.pipe(
+ takeUntilDestroyed(this.destroyRef)
+ ).subscribe(
+ (newDsType: DatasourceType) => this.onDsTypeChanged(newDsType)
+ );
+ }
+
+ registerOnChange(fn: any): void {
+ this.propagateChange = fn;
+ }
+
+ registerOnTouched(_fn: any): void {
+ }
+
+ setDisabledState(isDisabled: boolean): void {
+ this.disabled = isDisabled;
+ if (isDisabled) {
+ this.dataSourceFormGroup.disable({emitEvent: false});
+ } else {
+ this.dataSourceFormGroup.enable({emitEvent: false});
+ this.updateValidators();
+ }
+ }
+
+ writeValue(value: AdditionalMapDataSourceSettings): void {
+ this.modelValue = value;
+ this.dataSourceFormGroup.patchValue(
+ {
+ dsType: value?.dsType,
+ dsLabel: value?.dsLabel,
+ dsDeviceId: value?.dsDeviceId,
+ dsEntityAliasId: value?.dsEntityAliasId,
+ dataKeys: value?.dataKeys
+ }, {emitEvent: false}
+ );
+ this.updateValidators();
+ this.cd.markForCheck();
+ }
+
+ private generateDataKey(key: DataKey): DataKey {
+ const dataKey = this.context.callbacks.generateDataKey(key.name, key.type, null, false, null);
+ const dataKeys: DataKey[] = this.dataSourceFormGroup.get('dataKeys').value || [];
+ dataKey.label = genNextLabelForDataKeys(dataKey.label, dataKeys);
+ return dataKey;
+ }
+
+ private onDsTypeChanged(newDsType: DatasourceType) {
+ let updateModel = false;
+ const dataKeys: DataKey[] = this.dataSourceFormGroup.get('dataKeys').value;
+ if (dataKeys?.length) {
+ for (const key of dataKeys) {
+ updateModel = this.updateDataKeyToNewDsType(key, newDsType) || updateModel;
+ }
+ if (updateModel) {
+ this.dataSourceFormGroup.get('dataKeys').patchValue(dataKeys, {emitEvent: false});
+ }
+ }
+ this.updateValidators();
+ if (updateModel) {
+ this.updateModel();
+ }
+ }
+
+ private updateDataKeyToNewDsType(dataKey: DataKey, newDsType: DatasourceType): boolean {
+ if (newDsType === DatasourceType.function) {
+ if (dataKey.type !== DataKeyType.function) {
+ dataKey.type = DataKeyType.function;
+ return true;
+ }
+ } else {
+ if (dataKey.type === DataKeyType.function) {
+ dataKey.type = DataKeyType.attribute;
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private updateValidators() {
+ const dsType: DatasourceType = this.dataSourceFormGroup.get('dsType').value;
+ if (dsType === DatasourceType.function) {
+ this.dataSourceFormGroup.get('dsLabel').enable({emitEvent: false});
+ this.dataSourceFormGroup.get('dsDeviceId').disable({emitEvent: false});
+ this.dataSourceFormGroup.get('dsEntityAliasId').disable({emitEvent: false});
+ } else if (dsType === DatasourceType.device) {
+ this.dataSourceFormGroup.get('dsLabel').disable({emitEvent: false});
+ this.dataSourceFormGroup.get('dsDeviceId').enable({emitEvent: false});
+ this.dataSourceFormGroup.get('dsEntityAliasId').disable({emitEvent: false});
+ } else {
+ this.dataSourceFormGroup.get('dsLabel').disable({emitEvent: false});
+ this.dataSourceFormGroup.get('dsDeviceId').disable({emitEvent: false});
+ this.dataSourceFormGroup.get('dsEntityAliasId').enable({emitEvent: false});
+ }
+ }
+
+ private updateModel() {
+ this.modelValue = {...this.modelValue, ...this.dataSourceFormGroup.value};
+ this.propagateChange(this.modelValue);
+ }
+}
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/additional-map-data-sources.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/additional-map-data-sources.component.html
new file mode 100644
index 0000000000..7492dd90ff
--- /dev/null
+++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/additional-map-data-sources.component.html
@@ -0,0 +1,43 @@
+
+
+
+ {{ 'widgets.maps.data-layer.no-datasources' | translate }}
+
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/additional-map-data-sources.component.scss b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/additional-map-data-sources.component.scss
new file mode 100644
index 0000000000..de4e639051
--- /dev/null
+++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/additional-map-data-sources.component.scss
@@ -0,0 +1,53 @@
+/**
+ * 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 '../../../../../../../../../scss/constants';
+
+.tb-additional-map-data-sources {
+ .tb-form-table-header-cell {
+ &.tb-source-header {
+ flex: 1 1 50%;
+ }
+ &.tb-data-keys-header {
+ flex: 1 1 50%;
+ }
+ &.tb-actions-header {
+ width: 40px;
+ min-width: 40px;
+ }
+ @media #{$mat-lt-lg} {
+ &.tb-source-header {
+ flex: 1 1 30%;
+ }
+ &.tb-data-keys-header {
+ flex: 1 1 70%;
+ }
+ @media #{$mat-lt-md} {
+ &.tb-source-header {
+ flex: 1 1 50%;
+ }
+ &.tb-data-keys-header {
+ flex: 1 1 50%;
+ }
+ }
+ }
+ }
+
+ .tb-form-table-body {
+ tb-additional-map-data-source-row {
+ overflow: hidden;
+ }
+ }
+}
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/additional-map-data-sources.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/additional-map-data-sources.component.ts
new file mode 100644
index 0000000000..5cb2ade353
--- /dev/null
+++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/additional-map-data-sources.component.ts
@@ -0,0 +1,147 @@
+///
+/// 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, ViewEncapsulation } from '@angular/core';
+import {
+ AbstractControl,
+ ControlValueAccessor,
+ NG_VALIDATORS,
+ NG_VALUE_ACCESSOR,
+ UntypedFormArray,
+ UntypedFormBuilder,
+ UntypedFormControl,
+ UntypedFormGroup,
+ Validator
+} from '@angular/forms';
+import { mergeDeep } from '@core/utils';
+import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
+import {
+ AdditionalMapDataSourceSettings,
+ additionalMapDataSourceValid,
+ additionalMapDataSourceValidator,
+ defaultAdditionalMapDataSourceSettings
+} from '@shared/models/widget/maps/map.models';
+import { MapSettingsContext } from '@home/components/widget/lib/settings/common/map/map-settings.component.models';
+
+@Component({
+ selector: 'tb-additional-map-data-sources',
+ templateUrl: './additional-map-data-sources.component.html',
+ styleUrls: ['./additional-map-data-sources.component.scss'],
+ providers: [
+ {
+ provide: NG_VALUE_ACCESSOR,
+ useExisting: forwardRef(() => AdditionalMapDataSourcesComponent),
+ multi: true
+ },
+ {
+ provide: NG_VALIDATORS,
+ useExisting: forwardRef(() => AdditionalMapDataSourcesComponent),
+ multi: true
+ }
+ ],
+ encapsulation: ViewEncapsulation.None
+})
+export class AdditionalMapDataSourcesComponent implements ControlValueAccessor, OnInit, Validator {
+
+ @Input()
+ disabled: boolean;
+
+ @Input()
+ context: MapSettingsContext;
+
+ dataSourcesFormGroup: UntypedFormGroup;
+
+ private propagateChange = (_val: any) => {};
+
+ constructor(private fb: UntypedFormBuilder,
+ private destroyRef: DestroyRef) {
+ }
+
+ ngOnInit() {
+ this.dataSourcesFormGroup = this.fb.group({
+ dataSources: [this.fb.array([]), []]
+ });
+ this.dataSourcesFormGroup.valueChanges.pipe(
+ takeUntilDestroyed(this.destroyRef)
+ ).subscribe(
+ () => {
+ let dataSources: AdditionalMapDataSourceSettings[] = this.dataSourcesFormGroup.get('dataSources').value;
+ if (dataSources) {
+ dataSources = dataSources.filter(dataSource => additionalMapDataSourceValid(dataSource));
+ }
+ this.propagateChange(dataSources);
+ }
+ );
+ }
+
+ registerOnChange(fn: any): void {
+ this.propagateChange = fn;
+ }
+
+ registerOnTouched(fn: any): void {
+ }
+
+ setDisabledState(isDisabled: boolean): void {
+ this.disabled = isDisabled;
+ if (isDisabled) {
+ this.dataSourcesFormGroup.disable({emitEvent: false});
+ } else {
+ this.dataSourcesFormGroup.enable({emitEvent: false});
+ }
+ }
+
+ writeValue(value: AdditionalMapDataSourceSettings[] | undefined): void {
+ const dataSources: AdditionalMapDataSourceSettings[] = value || [];
+ this.dataSourcesFormGroup.setControl('dataSources', this.prepareDataSourcesFormArray(dataSources), {emitEvent: false});
+ }
+
+ public validate(c: UntypedFormControl) {
+ const valid = this.dataSourcesFormGroup.valid;
+ return valid ? null : {
+ dataSources: {
+ valid: false,
+ },
+ };
+ }
+
+ dataSourcesFormArray(): UntypedFormArray {
+ return this.dataSourcesFormGroup.get('dataSources') as UntypedFormArray;
+ }
+
+ trackByDataSource(index: number, dataSourceControl: AbstractControl): any {
+ return dataSourceControl;
+ }
+
+ removeDataSource(index: number) {
+ (this.dataSourcesFormGroup.get('dataSources') as UntypedFormArray).removeAt(index);
+ }
+
+ addDataSource() {
+ const dataSource = mergeDeep({} as AdditionalMapDataSourceSettings,
+ defaultAdditionalMapDataSourceSettings(this.context.functionsOnly));
+ const dataSourcesArray = this.dataSourcesFormGroup.get('dataSources') as UntypedFormArray;
+ const dataSourceControl = this.fb.control(dataSource, [additionalMapDataSourceValidator]);
+ dataSourcesArray.push(dataSourceControl);
+ }
+
+ private prepareDataSourcesFormArray(dataSources: AdditionalMapDataSourceSettings[]): UntypedFormArray {
+ const dataSourcesControls: Array = [];
+ dataSources.forEach((dataSource) => {
+ dataSourcesControls.push(this.fb.control(dataSource, [additionalMapDataSourceValidator]));
+ });
+ return this.fb.array(dataSourcesControls);
+ }
+}
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/data-layer-color-settings-panel.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/data-layer-color-settings-panel.component.html
index 281f20ccd4..3501559523 100644
--- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/data-layer-color-settings-panel.component.html
+++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/data-layer-color-settings-panel.component.html
@@ -39,7 +39,7 @@
-
+
+
+
+ widgets.maps.data-layer.more-datasources
+
+
+
+
+
+
+
-
-
+ formControlName="dsFilterId"
+ [callbacks]="context.callbacks">
+