Merge branch 'rc' of github.com:thingsboard/thingsboard into rc

This commit is contained in:
Igor Kulikov 2025-04-01 17:44:10 +03:00
commit 7de5ddaaa7
22 changed files with 128 additions and 109 deletions

View File

@ -135,8 +135,8 @@ export class WidgetActionDialogComponent extends DialogComponent<WidgetActionDia
showIcon: [{ value: this.action.showIcon ?? true, disabled: true}, []], showIcon: [{ value: this.action.showIcon ?? true, disabled: true}, []],
icon: [this.action.icon, Validators.required], icon: [this.action.icon, Validators.required],
buttonColor: [{ value: this.action.buttonColor ?? this.defaultIconColor, disabled: true}, []], buttonColor: [{ value: this.action.buttonColor ?? this.defaultIconColor, disabled: true}, []],
buttonFillColor: [{ value: this.action.buttonFillColor ?? '#3F52DD', disabled: true}, []], buttonFillColor: [{ value: this.action.buttonFillColor ?? '#305680', disabled: true}, []],
buttonBorderColor: [{ value: this.action.buttonBorderColor ?? '#3F52DD', disabled: true}, []], buttonBorderColor: [{ value: this.action.buttonBorderColor ?? '#0000001F', disabled: true}, []],
customButtonStyle: [{ value: this.action.customButtonStyle ?? {}, disabled: true}, []], customButtonStyle: [{ value: this.action.customButtonStyle ?? {}, disabled: true}, []],
useShowWidgetActionFunction: [this.action.useShowWidgetActionFunction], useShowWidgetActionFunction: [this.action.useShowWidgetActionFunction],
showWidgetActionFunction: [this.action.showWidgetActionFunction || 'return true;'], showWidgetActionFunction: [this.action.showWidgetActionFunction || 'return true;'],
@ -146,7 +146,7 @@ export class WidgetActionDialogComponent extends DialogComponent<WidgetActionDia
if (this.widgetActionFormGroup.get('actionSourceId').value === 'headerButton') { if (this.widgetActionFormGroup.get('actionSourceId').value === 'headerButton') {
this.widgetActionFormGroup.get('buttonType').enable({emitEvent: false}); this.widgetActionFormGroup.get('buttonType').enable({emitEvent: false});
this.widgetActionFormGroup.get('buttonColor').enable({emitEvent: false}); this.widgetActionFormGroup.get('buttonColor').enable({emitEvent: false});
this.widgetHeaderButtonValidators(); this.widgetHeaderButtonValidators(true);
} }
this.widgetActionFormGroup.get('actionSourceId').valueChanges.pipe( this.widgetActionFormGroup.get('actionSourceId').valueChanges.pipe(
takeUntilDestroyed(this.destroyRef) takeUntilDestroyed(this.destroyRef)
@ -162,7 +162,7 @@ export class WidgetActionDialogComponent extends DialogComponent<WidgetActionDia
if (value === 'headerButton') { if (value === 'headerButton') {
this.widgetActionFormGroup.get('buttonType').enable({emitEvent: false}); this.widgetActionFormGroup.get('buttonType').enable({emitEvent: false});
this.widgetActionFormGroup.get('buttonColor').enable({emitEvent: false}); this.widgetActionFormGroup.get('buttonColor').enable({emitEvent: false});
this.widgetHeaderButtonValidators(); this.widgetHeaderButtonValidators(true);
} else { } else {
this.widgetActionFormGroup.get('buttonType').disable({emitEvent: false}); this.widgetActionFormGroup.get('buttonType').disable({emitEvent: false});
this.widgetActionFormGroup.get('showIcon').disable({emitEvent: false}); this.widgetActionFormGroup.get('showIcon').disable({emitEvent: false});
@ -190,8 +190,17 @@ export class WidgetActionDialogComponent extends DialogComponent<WidgetActionDia
}); });
} }
widgetHeaderButtonValidators() { private widgetHeaderButtonValidators(ignoreUpdatedButtonColor = false) {
const buttonType = this.widgetActionFormGroup.get('buttonType').value; const buttonType = this.widgetActionFormGroup.get('buttonType').value;
if (!ignoreUpdatedButtonColor) {
if ([WidgetHeaderActionButtonType.raised, WidgetHeaderActionButtonType.flat, WidgetHeaderActionButtonType.miniFab].includes(buttonType)) {
this.widgetActionFormGroup.get('buttonColor').patchValue('#ffffff', {emitEvent: false});
} else if ([WidgetHeaderActionButtonType.stroked].includes(buttonType)) {
this.widgetActionFormGroup.get('buttonColor').patchValue('#305680', {emitEvent: false});
} else {
this.widgetActionFormGroup.get('buttonColor').patchValue(this.defaultIconColor, {emitEvent: false});
}
}
this.widgetActionFormGroup.get('showIcon').disable({emitEvent: false}); this.widgetActionFormGroup.get('showIcon').disable({emitEvent: false});
this.widgetActionFormGroup.get('buttonFillColor').disable({emitEvent: false}); this.widgetActionFormGroup.get('buttonFillColor').disable({emitEvent: false});
this.widgetActionFormGroup.get('buttonBorderColor').disable({emitEvent: false}); this.widgetActionFormGroup.get('buttonBorderColor').disable({emitEvent: false});

View File

@ -793,7 +793,7 @@ export class TbTimeSeriesChart {
} }
} else { } else {
if (!axis.option.name) { 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; result.changed = true;
} }
const nameGap = size; const nameGap = size;

View File

@ -95,7 +95,7 @@
{{ 'widgets.table.display-pagination' | translate }} {{ 'widgets.table.display-pagination' | translate }}
</mat-slide-toggle> </mat-slide-toggle>
<div class="tb-form-row space-between column-xs"> <div class="tb-form-row space-between column-xs">
<div class="fixed-title-width">{{ 'widgets.table.page-step-settings' | translate }}</div> <div class="fixed-title-width !min-w-30">{{ 'widgets.table.page-step-settings' | translate }}</div>
<div class="flex flex-row items-center justify-start gap-2"> <div class="flex flex-row items-center justify-start gap-2">
<div class="tb-small-label" translate>widgets.table.page-step-increment</div> <div class="tb-small-label" translate>widgets.table.page-step-increment</div>
<mat-form-field appearance="outline" class="number" subscriptSizing="dynamic"> <mat-form-field appearance="outline" class="number" subscriptSizing="dynamic">
@ -113,7 +113,7 @@
warning warning
</mat-icon> </mat-icon>
</mat-form-field> </mat-form-field>
<mat-divider vertical></mat-divider> <mat-divider vertical class="xs:hidden"></mat-divider>
<div class="tb-small-label" translate>widgets.table.page-step-count</div> <div class="tb-small-label" translate>widgets.table.page-step-count</div>
<mat-form-field appearance="outline" class="number" subscriptSizing="dynamic"> <mat-form-field appearance="outline" class="number" subscriptSizing="dynamic">
<input matInput formControlName="pageStepCount" type="number" min="1" max="100" step="1" required <input matInput formControlName="pageStepCount" type="number" min="1" max="100" step="1" required

View File

@ -66,6 +66,12 @@ export class AlarmsTableWidgetSettingsComponent extends WidgetSettingsComponent
}; };
} }
protected prepareInputSettings(settings: WidgetSettings): WidgetSettings {
settings.pageStepIncrement = settings.pageStepIncrement ?? settings.defaultPageSize;
this.pageStepSizeValues = buildPageStepSizeValues(settings.pageStepCount, settings.pageStepIncrement);
return settings;
}
protected onSettingsSet(settings: WidgetSettings) { protected onSettingsSet(settings: WidgetSettings) {
this.alarmsTableWidgetSettingsForm = this.fb.group({ this.alarmsTableWidgetSettingsForm = this.fb.group({
alarmsTitle: [settings.alarmsTitle, []], alarmsTitle: [settings.alarmsTitle, []],
@ -86,14 +92,11 @@ export class AlarmsTableWidgetSettingsComponent extends WidgetSettingsComponent
defaultPageSize: [settings.defaultPageSize, [Validators.min(1)]], defaultPageSize: [settings.defaultPageSize, [Validators.min(1)]],
pageStepCount: [settings.pageStepCount ?? 3, [Validators.min(1), Validators.max(100), pageStepCount: [settings.pageStepCount ?? 3, [Validators.min(1), Validators.max(100),
Validators.required, Validators.pattern(/^\d*$/)]], Validators.required, Validators.pattern(/^\d*$/)]],
pageStepIncrement: [settings.pageStepIncrement ?? settings.defaultPageSize, pageStepIncrement: [settings.pageStepIncrement, [Validators.min(1), Validators.required, Validators.pattern(/^\d*$/)]],
[Validators.min(1), Validators.required, Validators.pattern(/^\d*$/)]],
defaultSortOrder: [settings.defaultSortOrder, []], defaultSortOrder: [settings.defaultSortOrder, []],
useRowStyleFunction: [settings.useRowStyleFunction, []], useRowStyleFunction: [settings.useRowStyleFunction, []],
rowStyleFunction: [settings.rowStyleFunction, [Validators.required]] rowStyleFunction: [settings.rowStyleFunction, [Validators.required]]
}); });
this.pageStepSizeValues = buildPageStepSizeValues(this.alarmsTableWidgetSettingsForm.get('pageStepCount').value,
this.alarmsTableWidgetSettingsForm.get('pageStepIncrement').value);
} }
protected validatorTriggers(): string[] { protected validatorTriggers(): string[] {

View File

@ -62,7 +62,7 @@
{{ 'widgets.table.display-pagination' | translate }} {{ 'widgets.table.display-pagination' | translate }}
</mat-slide-toggle> </mat-slide-toggle>
<div class="tb-form-row space-between column-xs"> <div class="tb-form-row space-between column-xs">
<div class="fixed-title-width">{{ 'widgets.table.page-step-settings' | translate }}</div> <div class="fixed-title-width !min-w-30">{{ 'widgets.table.page-step-settings' | translate }}</div>
<div class="flex flex-row items-center justify-start gap-2"> <div class="flex flex-row items-center justify-start gap-2">
<div class="tb-small-label" translate>widgets.table.page-step-increment</div> <div class="tb-small-label" translate>widgets.table.page-step-increment</div>
<mat-form-field appearance="outline" class="number" subscriptSizing="dynamic"> <mat-form-field appearance="outline" class="number" subscriptSizing="dynamic">
@ -80,7 +80,7 @@
warning warning
</mat-icon> </mat-icon>
</mat-form-field> </mat-form-field>
<mat-divider vertical></mat-divider> <mat-divider vertical class="xs:hidden"></mat-divider>
<div class="tb-small-label" translate>widgets.table.page-step-count</div> <div class="tb-small-label" translate>widgets.table.page-step-count</div>
<mat-form-field appearance="outline" class="number" subscriptSizing="dynamic"> <mat-form-field appearance="outline" class="number" subscriptSizing="dynamic">
<input matInput formControlName="pageStepCount" type="number" min="1" max="100" step="1" required <input matInput formControlName="pageStepCount" type="number" min="1" max="100" step="1" required

View File

@ -62,6 +62,12 @@ export class TimeseriesTableWidgetSettingsComponent extends WidgetSettingsCompon
}; };
} }
protected prepareInputSettings(settings: WidgetSettings): WidgetSettings {
settings.pageStepIncrement = settings.pageStepIncrement ?? settings.defaultPageSize;
this.pageStepSizeValues = buildPageStepSizeValues(settings.pageStepCount, settings.pageStepIncrement);
return settings;
}
protected onSettingsSet(settings: WidgetSettings) { protected onSettingsSet(settings: WidgetSettings) {
// For backward compatibility // For backward compatibility
const dateFormat = settings.dateFormat; const dateFormat = settings.dateFormat;
@ -83,15 +89,12 @@ export class TimeseriesTableWidgetSettingsComponent extends WidgetSettingsCompon
defaultPageSize: [settings.defaultPageSize, [Validators.min(1)]], defaultPageSize: [settings.defaultPageSize, [Validators.min(1)]],
pageStepCount: [settings.pageStepCount ?? 3, [Validators.min(1), Validators.max(100), pageStepCount: [settings.pageStepCount ?? 3, [Validators.min(1), Validators.max(100),
Validators.required, Validators.pattern(/^\d*$/)]], Validators.required, Validators.pattern(/^\d*$/)]],
pageStepIncrement: [settings.pageStepIncrement ?? settings.defaultPageSize, pageStepIncrement: [settings.pageStepIncrement, [Validators.min(1), Validators.required, Validators.pattern(/^\d*$/)]],
[Validators.min(1), Validators.required, Validators.pattern(/^\d*$/)]],
hideEmptyLines: [settings.hideEmptyLines, []], hideEmptyLines: [settings.hideEmptyLines, []],
disableStickyHeader: [settings.disableStickyHeader, []], disableStickyHeader: [settings.disableStickyHeader, []],
useRowStyleFunction: [settings.useRowStyleFunction, []], useRowStyleFunction: [settings.useRowStyleFunction, []],
rowStyleFunction: [settings.rowStyleFunction, [Validators.required]] rowStyleFunction: [settings.rowStyleFunction, [Validators.required]]
}); });
this.pageStepSizeValues = buildPageStepSizeValues(this.timeseriesTableWidgetSettingsForm.get('pageStepCount').value,
this.timeseriesTableWidgetSettingsForm.get('pageStepIncrement').value);
} }
protected validatorTriggers(): string[] { protected validatorTriggers(): string[] {

View File

@ -105,6 +105,17 @@ export class TimeSeriesChartWidgetSettingsComponent extends WidgetSettingsCompon
const params = widgetConfig.typeParameters as any; const params = widgetConfig.typeParameters as any;
if (isDefinedAndNotNull(params.chartType)) { if (isDefinedAndNotNull(params.chartType)) {
this.chartType = 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 });
}
} }
} }

View File

@ -37,7 +37,7 @@
<div translate>widget-config.action</div> <div translate>widget-config.action</div>
<mat-form-field class="flex-1" appearance="outline" subscriptSizing="dynamic"> <mat-form-field class="flex-1" appearance="outline" subscriptSizing="dynamic">
<mat-select required formControlName="type" placeholder="{{ 'widget-config.set' | translate }}"> <mat-select required formControlName="type" placeholder="{{ 'widget-config.set' | translate }}">
<mat-option *ngFor="let actionType of widgetActionTypes" [value]="actionType"> <mat-option *ngFor="let actionType of actionTypes()" [value]="actionType">
{{ widgetActionTypeTranslations.get(widgetActionType[actionType]) | translate }} {{ widgetActionTypeTranslations.get(widgetActionType[actionType]) | translate }}
</mat-option> </mat-option>
</mat-select> </mat-select>

View File

@ -26,7 +26,7 @@ import {
ValidatorFn, ValidatorFn,
Validators Validators
} from '@angular/forms'; } 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 { import {
MapItemType, MapItemType,
mapItemTypeTranslationMap, mapItemTypeTranslationMap,
@ -102,19 +102,16 @@ export class WidgetActionComponent implements ControlValueAccessor, OnInit, Vali
@Input() @Input()
actionNames: string[]; actionNames: string[];
@Input() additionalWidgetActionTypes = input<WidgetActionType[]>(null);
set additionalWidgetActionTypes(value: WidgetActionType[]) {
if (this.widgetActionFormGroup && !widgetActionTypes.includes(this.widgetActionFormGroup.get('type').value)) { actionTypes = computed(() => {
this.widgetActionFormGroup.get('type').setValue(WidgetActionType.doNothing); const predefinedActionTypes = widgetActionTypes;
} if (this.additionalWidgetActionTypes()?.length) {
if (value?.length) { return predefinedActionTypes.concat(this.additionalWidgetActionTypes());
this.widgetActionTypes = widgetActionTypes.concat(value); }
} else { return predefinedActionTypes;
this.widgetActionTypes = widgetActionTypes; });
}
}
widgetActionTypes = widgetActionTypes;
widgetActionTypeTranslations = widgetActionTypeTranslationMap; widgetActionTypeTranslations = widgetActionTypeTranslationMap;
widgetActionType = WidgetActionType; widgetActionType = WidgetActionType;
@ -191,9 +188,6 @@ export class WidgetActionComponent implements ControlValueAccessor, OnInit, Vali
).subscribe(() => { ).subscribe(() => {
this.widgetActionUpdated(); this.widgetActionUpdated();
}); });
if (this.additionalWidgetActionTypes) {
this.widgetActionTypes = this.widgetActionTypes.concat(this.additionalWidgetActionTypes);
}
} }
writeValue(widgetAction?: WidgetAction): void { writeValue(widgetAction?: WidgetAction): void {

View File

@ -33,7 +33,7 @@ import {
Validators Validators
} from '@angular/forms'; } from '@angular/forms';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { AdditionalMapDataSourceSettings } from '@shared/models/widget/maps/map.models'; import { AdditionalMapDataSourceSettings, updateDataKeyToNewDsType } from '@shared/models/widget/maps/map.models';
import { DataKey, DatasourceType, datasourceTypeTranslationMap, widgetType } from '@shared/models/widget.models'; import { DataKey, DatasourceType, datasourceTypeTranslationMap, widgetType } from '@shared/models/widget.models';
import { EntityType } from '@shared/models/entity-type.models'; import { EntityType } from '@shared/models/entity-type.models';
import { DataKeyType } from '@shared/models/telemetry/telemetry.models'; import { DataKeyType } from '@shared/models/telemetry/telemetry.models';
@ -156,7 +156,7 @@ export class AdditionalMapDataSourceRowComponent implements ControlValueAccessor
const dataKeys: DataKey[] = this.dataSourceFormGroup.get('dataKeys').value; const dataKeys: DataKey[] = this.dataSourceFormGroup.get('dataKeys').value;
if (dataKeys?.length) { if (dataKeys?.length) {
for (const key of dataKeys) { for (const key of dataKeys) {
updateModel = this.updateDataKeyToNewDsType(key, newDsType) || updateModel; updateModel = updateDataKeyToNewDsType(key, newDsType) || updateModel;
} }
if (updateModel) { if (updateModel) {
this.dataSourceFormGroup.get('dataKeys').patchValue(dataKeys, {emitEvent: false}); this.dataSourceFormGroup.get('dataKeys').patchValue(dataKeys, {emitEvent: false});
@ -168,21 +168,6 @@ export class AdditionalMapDataSourceRowComponent implements ControlValueAccessor
} }
} }
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() { private updateValidators() {
const dsType: DatasourceType = this.dataSourceFormGroup.get('dsType').value; const dsType: DatasourceType = this.dataSourceFormGroup.get('dsType').value;
if (dsType === DatasourceType.function) { if (dsType === DatasourceType.function) {

View File

@ -31,7 +31,8 @@ import {
pathDecoratorSymbolTranslationMap, pathDecoratorSymbolTranslationMap,
PolygonsDataLayerSettings, PolygonsDataLayerSettings,
ShapeDataLayerSettings, ShapeDataLayerSettings,
TripsDataLayerSettings TripsDataLayerSettings,
updateDataKeyToNewDsType
} from '@shared/models/widget/maps/map.models'; } from '@shared/models/widget/maps/map.models';
import { Store } from '@ngrx/store'; import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state'; import { AppState } from '@core/core.state';
@ -294,23 +295,23 @@ export class MapDataLayerDialogComponent extends DialogComponent<MapDataLayerDia
case 'trips': case 'trips':
case 'markers': case 'markers':
const xKey: DataKey = this.dataLayerFormGroup.get('xKey').value; const xKey: DataKey = this.dataLayerFormGroup.get('xKey').value;
if (this.updateDataKeyToNewDsType(xKey, newDsType, this.dataLayerType === 'trips')) { if (updateDataKeyToNewDsType(xKey, newDsType, this.dataLayerType === 'trips')) {
this.dataLayerFormGroup.get('xKey').patchValue(xKey, {emitEvent: false}); this.dataLayerFormGroup.get('xKey').patchValue(xKey, {emitEvent: false});
} }
const yKey: DataKey = this.dataLayerFormGroup.get('yKey').value; const yKey: DataKey = this.dataLayerFormGroup.get('yKey').value;
if (this.updateDataKeyToNewDsType(yKey, newDsType, this.dataLayerType === 'trips')) { if (updateDataKeyToNewDsType(yKey, newDsType, this.dataLayerType === 'trips')) {
this.dataLayerFormGroup.get('yKey').patchValue(yKey, {emitEvent: false}); this.dataLayerFormGroup.get('yKey').patchValue(yKey, {emitEvent: false});
} }
break; break;
case 'polygons': case 'polygons':
const polygonKey: DataKey = this.dataLayerFormGroup.get('polygonKey').value; const polygonKey: DataKey = this.dataLayerFormGroup.get('polygonKey').value;
if (this.updateDataKeyToNewDsType(polygonKey, newDsType)) { if (updateDataKeyToNewDsType(polygonKey, newDsType)) {
this.dataLayerFormGroup.get('polygonKey').patchValue(polygonKey, {emitEvent: false}); this.dataLayerFormGroup.get('polygonKey').patchValue(polygonKey, {emitEvent: false});
} }
break; break;
case 'circles': case 'circles':
const circleKey: DataKey = this.dataLayerFormGroup.get('circleKey').value; const circleKey: DataKey = this.dataLayerFormGroup.get('circleKey').value;
if (this.updateDataKeyToNewDsType(circleKey, newDsType)) { if (updateDataKeyToNewDsType(circleKey, newDsType)) {
this.dataLayerFormGroup.get('circleKey').patchValue(circleKey, {emitEvent: false}); this.dataLayerFormGroup.get('circleKey').patchValue(circleKey, {emitEvent: false});
} }
break; break;
@ -319,7 +320,7 @@ export class MapDataLayerDialogComponent extends DialogComponent<MapDataLayerDia
if (additionalDataKeys?.length) { if (additionalDataKeys?.length) {
let updated = false; let updated = false;
for (const key of additionalDataKeys) { for (const key of additionalDataKeys) {
updated = this.updateDataKeyToNewDsType(key, newDsType) || updated; updated = updateDataKeyToNewDsType(key, newDsType) || updated;
} }
if (updated) { if (updated) {
this.dataLayerFormGroup.get('additionalDataKeys').patchValue(additionalDataKeys, {emitEvent: false}); this.dataLayerFormGroup.get('additionalDataKeys').patchValue(additionalDataKeys, {emitEvent: false});
@ -328,21 +329,6 @@ export class MapDataLayerDialogComponent extends DialogComponent<MapDataLayerDia
this.updateValidators(); this.updateValidators();
} }
private updateDataKeyToNewDsType(dataKey: DataKey, newDsType: DatasourceType, timeSeries = false): boolean {
if (newDsType === DatasourceType.function) {
if (dataKey.type !== DataKeyType.function) {
dataKey.type = DataKeyType.function;
return true;
}
} else {
if (dataKey.type === DataKeyType.function) {
dataKey.type = timeSeries ? DataKeyType.timeseries : DataKeyType.attribute;
return true;
}
}
return false;
}
private updateValidators() { private updateValidators() {
const dsType: DatasourceType = this.dataLayerFormGroup.get('dsType').value; const dsType: DatasourceType = this.dataLayerFormGroup.get('dsType').value;
if (dsType === DatasourceType.function) { if (dsType === DatasourceType.function) {

View File

@ -41,7 +41,8 @@ import {
MapType, MapType,
MarkersDataLayerSettings, MarkersDataLayerSettings,
PolygonsDataLayerSettings, PolygonsDataLayerSettings,
TripsDataLayerSettings TripsDataLayerSettings,
updateDataKeyToNewDsType
} from '@shared/models/widget/maps/map.models'; } from '@shared/models/widget/maps/map.models';
import { DataKey, DatasourceType, datasourceTypeTranslationMap, widgetType } from '@shared/models/widget.models'; import { DataKey, DatasourceType, datasourceTypeTranslationMap, widgetType } from '@shared/models/widget.models';
import { EntityType } from '@shared/models/entity-type.models'; import { EntityType } from '@shared/models/entity-type.models';
@ -271,26 +272,26 @@ export class MapDataLayerRowComponent implements ControlValueAccessor, OnInit {
case 'trips': case 'trips':
case 'markers': case 'markers':
const xKey: DataKey = this.dataLayerFormGroup.get('xKey').value; const xKey: DataKey = this.dataLayerFormGroup.get('xKey').value;
if (this.updateDataKeyToNewDsType(xKey, newDsType, this.dataLayerType === 'trips')) { if (updateDataKeyToNewDsType(xKey, newDsType, this.dataLayerType === 'trips')) {
this.dataLayerFormGroup.get('xKey').patchValue(xKey, {emitEvent: false}); this.dataLayerFormGroup.get('xKey').patchValue(xKey, {emitEvent: false});
updateModel = true; updateModel = true;
} }
const yKey: DataKey = this.dataLayerFormGroup.get('yKey').value; const yKey: DataKey = this.dataLayerFormGroup.get('yKey').value;
if (this.updateDataKeyToNewDsType(yKey, newDsType, this.dataLayerType === 'trips')) { if (updateDataKeyToNewDsType(yKey, newDsType, this.dataLayerType === 'trips')) {
this.dataLayerFormGroup.get('yKey').patchValue(yKey, {emitEvent: false}); this.dataLayerFormGroup.get('yKey').patchValue(yKey, {emitEvent: false});
updateModel = true; updateModel = true;
} }
break; break;
case 'polygons': case 'polygons':
const polygonKey: DataKey = this.dataLayerFormGroup.get('polygonKey').value; const polygonKey: DataKey = this.dataLayerFormGroup.get('polygonKey').value;
if (this.updateDataKeyToNewDsType(polygonKey, newDsType)) { if (updateDataKeyToNewDsType(polygonKey, newDsType)) {
this.dataLayerFormGroup.get('polygonKey').patchValue(polygonKey, {emitEvent: false}); this.dataLayerFormGroup.get('polygonKey').patchValue(polygonKey, {emitEvent: false});
updateModel = true; updateModel = true;
} }
break; break;
case 'circles': case 'circles':
const circleKey: DataKey = this.dataLayerFormGroup.get('circleKey').value; const circleKey: DataKey = this.dataLayerFormGroup.get('circleKey').value;
if (this.updateDataKeyToNewDsType(circleKey, newDsType)) { if (updateDataKeyToNewDsType(circleKey, newDsType)) {
this.dataLayerFormGroup.get('circleKey').patchValue(circleKey, {emitEvent: false}); this.dataLayerFormGroup.get('circleKey').patchValue(circleKey, {emitEvent: false});
updateModel = true; updateModel = true;
} }
@ -302,21 +303,6 @@ export class MapDataLayerRowComponent implements ControlValueAccessor, OnInit {
} }
} }
private updateDataKeyToNewDsType(dataKey: DataKey, newDsType: DatasourceType, timeSeries = false): boolean {
if (newDsType === DatasourceType.function) {
if (dataKey.type !== DataKeyType.function) {
dataKey.type = DataKeyType.function;
return true;
}
} else {
if (dataKey.type === DataKeyType.function) {
dataKey.type = timeSeries ? DataKeyType.timeseries : DataKeyType.attribute;
return true;
}
}
return false;
}
private updateValidators() { private updateValidators() {
const dsType: DatasourceType = this.dataLayerFormGroup.get('dsType').value; const dsType: DatasourceType = this.dataLayerFormGroup.get('dsType').value;
if (dsType === DatasourceType.function) { if (dsType === DatasourceType.function) {

View File

@ -14,7 +14,7 @@
/// limitations under the License. /// limitations under the License.
/// ///
import { Component, DestroyRef, forwardRef, Input, OnInit } from '@angular/core'; import { Component, DestroyRef, forwardRef, Input, OnChanges, OnInit, SimpleChanges } from '@angular/core';
import { import {
ControlValueAccessor, ControlValueAccessor,
NG_VALIDATORS, NG_VALIDATORS,
@ -70,7 +70,7 @@ import { MatDialog } from '@angular/material/dialog';
} }
] ]
}) })
export class MapSettingsComponent implements OnInit, ControlValueAccessor, Validator { export class MapSettingsComponent implements OnInit, ControlValueAccessor, Validator, OnChanges {
mapControlPositions = mapControlPositions; mapControlPositions = mapControlPositions;
@ -205,6 +205,23 @@ export class MapSettingsComponent implements OnInit, ControlValueAccessor, Valid
} }
} }
ngOnChanges(changes: SimpleChanges) {
if (changes.trip) {
const tripChange = changes.trip;
if (!tripChange.firstChange && tripChange.currentValue !== tripChange.previousValue) {
if (this.trip) {
this.dataLayerMode = 'trips'
this.mapSettingsFormGroup.addControl('trips', this.fb.control(this.modelValue.trips), {emitEvent: false});
this.mapSettingsFormGroup.addControl('tripTimeline', this.fb.control(this.modelValue.tripTimeline), {emitEvent: false});
} else {
this.dataLayerMode = 'markers';
this.mapSettingsFormGroup.removeControl('trips', {emitEvent: false});
this.mapSettingsFormGroup.removeControl('tripTimeline', {emitEvent: false});
}
}
}
}
writeValue(value: MapSetting): void { writeValue(value: MapSetting): void {
this.modelValue = value; this.modelValue = value;
this.mapSettingsFormGroup.patchValue( this.mapSettingsFormGroup.patchValue(

View File

@ -49,7 +49,7 @@
{{ 'widgets.table.display-pagination' | translate }} {{ 'widgets.table.display-pagination' | translate }}
</mat-slide-toggle> </mat-slide-toggle>
<div class="tb-form-row space-between column-xs"> <div class="tb-form-row space-between column-xs">
<div class="fixed-title-width">{{ 'widgets.table.page-step-settings' | translate }}</div> <div class="fixed-title-width !min-w-30">{{ 'widgets.table.page-step-settings' | translate }}</div>
<div class="flex flex-row items-center justify-start gap-2"> <div class="flex flex-row items-center justify-start gap-2">
<div class="tb-small-label" translate>widgets.table.page-step-increment</div> <div class="tb-small-label" translate>widgets.table.page-step-increment</div>
<mat-form-field appearance="outline" class="number" subscriptSizing="dynamic"> <mat-form-field appearance="outline" class="number" subscriptSizing="dynamic">
@ -67,7 +67,7 @@
warning warning
</mat-icon> </mat-icon>
</mat-form-field> </mat-form-field>
<mat-divider vertical></mat-divider> <mat-divider vertical class="xs:hidden"></mat-divider>
<div class="tb-small-label" translate>widgets.table.page-step-count</div> <div class="tb-small-label" translate>widgets.table.page-step-count</div>
<mat-form-field appearance="outline" class="number" subscriptSizing="dynamic"> <mat-form-field appearance="outline" class="number" subscriptSizing="dynamic">
<input matInput formControlName="pageStepCount" type="number" min="1" max="100" step="1" required <input matInput formControlName="pageStepCount" type="number" min="1" max="100" step="1" required

View File

@ -105,6 +105,12 @@ export class PersistentTableWidgetSettingsComponent extends WidgetSettingsCompon
}; };
} }
protected prepareInputSettings(settings: WidgetSettings): WidgetSettings {
settings.pageStepIncrement = settings.pageStepIncrement ?? settings.defaultPageSize;
this.pageStepSizeValues = buildPageStepSizeValues(settings.pageStepCount, settings.pageStepIncrement);
return settings;
}
protected onSettingsSet(settings: WidgetSettings) { protected onSettingsSet(settings: WidgetSettings) {
this.persistentTableWidgetSettingsForm = this.fb.group({ this.persistentTableWidgetSettingsForm = this.fb.group({
enableFilter: [settings.enableFilter, []], enableFilter: [settings.enableFilter, []],
@ -117,13 +123,10 @@ export class PersistentTableWidgetSettingsComponent extends WidgetSettingsCompon
defaultPageSize: [settings.defaultPageSize, [Validators.min(1)]], defaultPageSize: [settings.defaultPageSize, [Validators.min(1)]],
pageStepCount: [settings.pageStepCount ?? 3, [Validators.min(1), Validators.max(100), pageStepCount: [settings.pageStepCount ?? 3, [Validators.min(1), Validators.max(100),
Validators.required, Validators.pattern(/^\d*$/)]], Validators.required, Validators.pattern(/^\d*$/)]],
pageStepIncrement: [settings.pageStepIncrement ?? settings.defaultPageSize, pageStepIncrement: [settings.pageStepIncrement, [Validators.min(1), Validators.required, Validators.pattern(/^\d*$/)]],
[Validators.min(1), Validators.required, Validators.pattern(/^\d*$/)]],
defaultSortOrder: [settings.defaultSortOrder, []], defaultSortOrder: [settings.defaultSortOrder, []],
displayColumns: [settings.displayColumns, [Validators.required]] displayColumns: [settings.displayColumns, [Validators.required]]
}); });
this.pageStepSizeValues = buildPageStepSizeValues(this.persistentTableWidgetSettingsForm.get('pageStepCount').value,
this.persistentTableWidgetSettingsForm.get('pageStepIncrement').value);
} }
public validateSettings(): boolean { public validateSettings(): boolean {

View File

@ -92,7 +92,7 @@
{{ 'widgets.table.display-pagination' | translate }} {{ 'widgets.table.display-pagination' | translate }}
</mat-slide-toggle> </mat-slide-toggle>
<div class="tb-form-row space-between column-xs"> <div class="tb-form-row space-between column-xs">
<div class="fixed-title-width">{{ 'widgets.table.page-step-settings' | translate }}</div> <div class="fixed-title-width !min-w-30">{{ 'widgets.table.page-step-settings' | translate }}</div>
<div class="flex flex-row items-center justify-start gap-2"> <div class="flex flex-row items-center justify-start gap-2">
<div class="tb-small-label" translate>widgets.table.page-step-increment</div> <div class="tb-small-label" translate>widgets.table.page-step-increment</div>
<mat-form-field appearance="outline" class="number" subscriptSizing="dynamic"> <mat-form-field appearance="outline" class="number" subscriptSizing="dynamic">
@ -110,7 +110,7 @@
warning warning
</mat-icon> </mat-icon>
</mat-form-field> </mat-form-field>
<mat-divider vertical></mat-divider> <mat-divider vertical class="xs:hidden"></mat-divider>
<div class="tb-small-label" translate>widgets.table.page-step-count</div> <div class="tb-small-label" translate>widgets.table.page-step-count</div>
<mat-form-field appearance="outline" class="number" subscriptSizing="dynamic"> <mat-form-field appearance="outline" class="number" subscriptSizing="dynamic">
<input matInput formControlName="pageStepCount" type="number" min="1" max="100" step="1" required <input matInput formControlName="pageStepCount" type="number" min="1" max="100" step="1" required

View File

@ -64,6 +64,12 @@ export class EntitiesTableWidgetSettingsComponent extends WidgetSettingsComponen
}; };
} }
protected prepareInputSettings(settings: WidgetSettings): WidgetSettings {
settings.pageStepIncrement = settings.pageStepIncrement ?? settings.defaultPageSize;
this.pageStepSizeValues = buildPageStepSizeValues(settings.pageStepCount, settings.pageStepIncrement);
return settings;
}
protected onSettingsSet(settings: WidgetSettings) { protected onSettingsSet(settings: WidgetSettings) {
this.entitiesTableWidgetSettingsForm = this.fb.group({ this.entitiesTableWidgetSettingsForm = this.fb.group({
entitiesTitle: [settings.entitiesTitle, []], entitiesTitle: [settings.entitiesTitle, []],
@ -82,14 +88,11 @@ export class EntitiesTableWidgetSettingsComponent extends WidgetSettingsComponen
defaultPageSize: [settings.defaultPageSize, [Validators.min(1)]], defaultPageSize: [settings.defaultPageSize, [Validators.min(1)]],
pageStepCount: [settings.pageStepCount ?? 3, [Validators.min(1), Validators.max(100), pageStepCount: [settings.pageStepCount ?? 3, [Validators.min(1), Validators.max(100),
Validators.required, Validators.pattern(/^\d*$/)]], Validators.required, Validators.pattern(/^\d*$/)]],
pageStepIncrement: [settings.pageStepIncrement ?? settings.defaultPageSize, pageStepIncrement: [settings.pageStepIncrement, [Validators.min(1), Validators.required, Validators.pattern(/^\d*$/)]],
[Validators.min(1), Validators.required, Validators.pattern(/^\d*$/)]],
defaultSortOrder: [settings.defaultSortOrder, []], defaultSortOrder: [settings.defaultSortOrder, []],
useRowStyleFunction: [settings.useRowStyleFunction, []], useRowStyleFunction: [settings.useRowStyleFunction, []],
rowStyleFunction: [settings.rowStyleFunction, [Validators.required]] rowStyleFunction: [settings.rowStyleFunction, [Validators.required]]
}); });
this.pageStepSizeValues = buildPageStepSizeValues(this.entitiesTableWidgetSettingsForm.get('pageStepCount').value,
this.entitiesTableWidgetSettingsForm.get('pageStepIncrement').value);
} }
protected validatorTriggers(): string[] { protected validatorTriggers(): string[] {

View File

@ -47,6 +47,8 @@ export class MapWidgetSettingsComponent extends WidgetSettingsComponent {
const params = widgetConfig.typeParameters as any; const params = widgetConfig.typeParameters as any;
if (isDefinedAndNotNull(params.trip)) { if (isDefinedAndNotNull(params.trip)) {
this.trip = params.trip === true; this.trip = params.trip === true;
} else {
this.trip = false;
} }
} }

View File

@ -141,7 +141,7 @@ export const loadModuleMarkdownSourceCode = (http: HttpClient, translate: Transl
let sourceCode = `<div class="flex flex-col !pl-4"><h6>${resource.title}</h6><small>${translate.instant('js-func.source-code')}</small></div>\n\n`; let sourceCode = `<div class="flex flex-col !pl-4"><h6>${resource.title}</h6><small>${translate.instant('js-func.source-code')}</small></div>\n\n`;
return loadFunctionModuleSource(http, resource.link).pipe( return loadFunctionModuleSource(http, resource.link).pipe(
map((source) => { map((source) => {
sourceCode += '```javascript\n{:code-style="margin-left: -16px; margin-right: -16px;"}\n' + source + '\n```'; sourceCode += '```javascript\n{:code-style="margin-left: -16px; margin-right: -16px; max-height: 65vh;"}\n' + source + '\n```';
return sourceCode; return sourceCode;
}), }),
catchError(err => { catchError(err => {

View File

@ -1182,6 +1182,21 @@ export const parseCenterPosition = (position: string | [number, number]): [numbe
return [0, 0]; return [0, 0];
} }
export const updateDataKeyToNewDsType = (dataKey: DataKey | null, newDsType: DatasourceType, timeSeries = false): boolean => {
if (newDsType === DatasourceType.function) {
if (dataKey && dataKey.type !== DataKeyType.function) {
dataKey.type = DataKeyType.function;
return true;
}
} else {
if (dataKey?.type === DataKeyType.function) {
dataKey.type = timeSeries ? DataKeyType.timeseries : DataKeyType.attribute;
return true;
}
}
return false;
}
export const mergeMapDatasources = (target: TbMapDatasource[], source: TbMapDatasource[]): TbMapDatasource[] => { export const mergeMapDatasources = (target: TbMapDatasource[], source: TbMapDatasource[]): TbMapDatasource[] => {
const appendDatasources: TbMapDatasource[] = []; const appendDatasources: TbMapDatasource[] = [];
for (const sourceDs of source) { for (const sourceDs of source) {

View File

@ -360,9 +360,10 @@
} }
} }
.tb-small-label { .tb-small-label {
display: flex;
align-items: center;
height: 40px; height: 40px;
font-size: 14px; font-size: 14px;
line-height: 40px;
letter-spacing: 0.2px; letter-spacing: 0.2px;
color: rgba(0, 0, 0, 0.38); color: rgba(0, 0, 0, 0.38);
} }

View File

@ -102,6 +102,7 @@ module.exports = {
minWidth: { minWidth: {
'7.5': '1.875rem', '7.5': '1.875rem',
'25': '6.25rem', '25': '6.25rem',
'30': '7.5rem',
'37.5': '9.375rem', '37.5': '9.375rem',
'62.5': '15.625rem', '62.5': '15.625rem',
'72.5': '18.125rem', '72.5': '18.125rem',