This is an automated email from the ASF dual-hosted git repository.
riemer pushed a commit to branch dev
in repository https://gitbox.apache.org/repos/asf/streampipes.git
The following commit(s) were added to refs/heads/dev by this push:
new d219c102c6 feat: add reactive forms to Configuration/sites (#3618)
d219c102c6 is described below
commit d219c102c6119c3f4ffbad6d552a0f9bf37b128d
Author: Dominik Riemer <[email protected]>
AuthorDate: Thu May 22 08:22:37 2025 +0200
feat: add reactive forms to Configuration/sites (#3618)
Co-authored-by: Marcelfrueh <[email protected]>
---
ui/deployment/i18n/de.json | 2 +
ui/deployment/i18n/en.json | 2 +
ui/src/app/configuration/configuration.module.ts | 2 +
.../edit-location-area.component.html | 20 +++++++--
.../edit-location-area.component.scss | 11 +++++
.../edit-location-area.component.ts | 28 +++++++++++--
.../edit-location/edit-location.component.html | 12 ++++--
.../edit-location/edit-location.component.ts | 16 +++++++-
.../manage-site/manage-site-dialog.component.html | 2 +
.../manage-site/manage-site-dialog.component.ts | 13 +++++-
.../single-marker-map.component.ts | 47 +++++++++++++++++++++-
.../core-ui/static-properties/input.validator.ts | 15 +++++++
12 files changed, 154 insertions(+), 16 deletions(-)
diff --git a/ui/deployment/i18n/de.json b/ui/deployment/i18n/de.json
index 5b08c9c1d0..8c6208c308 100644
--- a/ui/deployment/i18n/de.json
+++ b/ui/deployment/i18n/de.json
@@ -288,6 +288,8 @@
"Authorized Groups": "Autorisierte Gruppen",
"Group selection": "Auswahl der Gruppe",
"(no log messages available)": "(keine Protokollmeldungen verfügbar)",
+ "Site label is required": "Standort-Label ist erforderlich",
+ "This site already exists": "Standort existiert bereits",
"success": "Erfolg",
"error": "Fehler",
"waiting": "Warten",
diff --git a/ui/deployment/i18n/en.json b/ui/deployment/i18n/en.json
index fd8a9cc6c0..78e13a4db4 100644
--- a/ui/deployment/i18n/en.json
+++ b/ui/deployment/i18n/en.json
@@ -288,6 +288,8 @@
"Authorized Groups": null,
"Group selection": null,
"(no log messages available)": null,
+ "Site label is required": null,
+ "This site already exists": null,
"success": null,
"error": null,
"waiting": null,
diff --git a/ui/src/app/configuration/configuration.module.ts
b/ui/src/app/configuration/configuration.module.ts
index accdfdec37..3e2b9e9930 100644
--- a/ui/src/app/configuration/configuration.module.ts
+++ b/ui/src/app/configuration/configuration.module.ts
@@ -99,6 +99,7 @@ import { MatDialogModule } from '@angular/material/dialog';
import { MatProgressBarModule } from '@angular/material/progress-bar';
import { GenericStorageItemComponent } from
'./export/export-dialog/generic-storage-items/generic-storage-item/generic-storage-item.component';
import { GenericStorageItemsComponent } from
'./export/export-dialog/generic-storage-items/generic-storage-items.component';
+import { TranslatePipe } from '@ngx-translate/core';
@NgModule({
imports: [
@@ -197,6 +198,7 @@ import { GenericStorageItemsComponent } from
'./export/export-dialog/generic-sto
MatSort,
MatListModule,
MatDialogModule,
+ TranslatePipe,
],
declarations: [
ServiceConfigsComponent,
diff --git
a/ui/src/app/configuration/dialog/manage-site/edit-location/edit-location-area/edit-location-area.component.html
b/ui/src/app/configuration/dialog/manage-site/edit-location/edit-location-area/edit-location-area.component.html
index ad0392c3e0..b3dbc836fa 100644
---
a/ui/src/app/configuration/dialog/manage-site/edit-location/edit-location-area/edit-location-area.component.html
+++
b/ui/src/app/configuration/dialog/manage-site/edit-location/edit-location-area/edit-location-area.component.html
@@ -16,7 +16,12 @@
~
-->
-<div fxLayout="column" class="w-100" fxLayoutGap="10px">
+<div
+ fxLayout="column"
+ class="w-100"
+ fxLayoutGap="10px"
+ [formGroup]="areaControlGroup"
+>
<div
*ngFor="let area of site.areas; let i = index"
fxLayout="row"
@@ -50,16 +55,25 @@
<input
data-cy="sites-dialog-new-area-input"
matInput
- [(ngModel)]="newArea"
+ formControlName="areaControl"
/>
+ <mat-error
+ *ngIf="
+ areaControlGroup
+ .get('areaControl')
+ .hasError('forbiddenName')
+ "
+ >{{ 'This site already exists' | translate }}</mat-error
+ >
</mat-form-field>
</div>
<div fxLayoutAlign="end center">
<button
data-cy="sites-dialog-add-area-button"
mat-icon-button
- color="accent"
+ class="custom-icon-button"
(click)="addNewArea()"
+ [disabled]="isAddAreaDisabled"
>
<mat-icon>add</mat-icon>
</button>
diff --git
a/ui/src/app/configuration/dialog/manage-site/edit-location/edit-location-area/edit-location-area.component.scss
b/ui/src/app/configuration/dialog/manage-site/edit-location/edit-location-area/edit-location-area.component.scss
index d1743a813a..8edf5ad359 100644
---
a/ui/src/app/configuration/dialog/manage-site/edit-location/edit-location-area/edit-location-area.component.scss
+++
b/ui/src/app/configuration/dialog/manage-site/edit-location/edit-location-area/edit-location-area.component.scss
@@ -26,3 +26,14 @@
padding: 5px;
font-weight: bold;
}
+
+.custom-icon-button {
+ margin-top: 1px;
+}
+
+.custom-icon-button:disabled {
+ color: grey;
+ opacity: 0.4;
+ cursor: not-allowed;
+ margin-top: 1px;
+}
diff --git
a/ui/src/app/configuration/dialog/manage-site/edit-location/edit-location-area/edit-location-area.component.ts
b/ui/src/app/configuration/dialog/manage-site/edit-location/edit-location-area/edit-location-area.component.ts
index ddd7baab40..c0d5fb379e 100644
---
a/ui/src/app/configuration/dialog/manage-site/edit-location/edit-location-area/edit-location-area.component.ts
+++
b/ui/src/app/configuration/dialog/manage-site/edit-location/edit-location-area/edit-location-area.component.ts
@@ -16,25 +16,45 @@
*
*/
-import { Component, Input } from '@angular/core';
+import { Component, Input, OnInit } from '@angular/core';
import { AssetSiteDesc } from '@streampipes/platform-services';
+import { FormControl, FormGroup } from '@angular/forms';
+import { checkForDuplicatesValidator } from
'../../../../../core-ui/static-properties/input.validator';
@Component({
selector: 'sp-edit-asset-location-area-component',
templateUrl: './edit-location-area.component.html',
styleUrls: ['./edit-location-area.component.scss'],
})
-export class EditAssetLocationAreaComponent {
+export class EditAssetLocationAreaComponent implements OnInit {
@Input()
site: AssetSiteDesc;
- newArea: string = '';
+ areaControlGroup: FormGroup;
+
+ ngOnInit(): void {
+ this.areaControlGroup = new FormGroup({
+ areaControl: new FormControl('', [
+ checkForDuplicatesValidator(() => this.site.areas),
+ ]),
+ });
+ }
addNewArea(): void {
- this.site.areas.push(this.newArea);
+ this.site.areas.push(this.areaControlGroup.get('areaControl').value);
+ this.areaControlGroup.get('areaControl').reset();
}
removeArea(area: string): void {
this.site.areas.splice(this.site.areas.indexOf(area), 1);
}
+
+ get isAddAreaDisabled(): boolean {
+ const value = this.areaControlGroup.get('areaControl')?.value;
+ const trimmedValue = value?.trim();
+ return (
+ !trimmedValue ||
+ this.areaControlGroup.get('areaControl').hasError('forbiddenName')
+ );
+ }
}
diff --git
a/ui/src/app/configuration/dialog/manage-site/edit-location/edit-location.component.html
b/ui/src/app/configuration/dialog/manage-site/edit-location/edit-location.component.html
index 0c24fba58f..a6f96db8c8 100644
---
a/ui/src/app/configuration/dialog/manage-site/edit-location/edit-location.component.html
+++
b/ui/src/app/configuration/dialog/manage-site/edit-location/edit-location.component.html
@@ -15,8 +15,7 @@
~ limitations under the License.
~
-->
-
-<div fxLayout="column">
+<div fxLayout="column" [formGroup]="siteAreaControl">
<sp-basic-field-description
fxFlex="100"
descriptionPanelWidth="30"
@@ -27,8 +26,14 @@
<input
data-cy="sites-dialog-site-input"
matInput
- [(ngModel)]="site.label"
+ formControlName="label"
+ placeholder="New site"
/>
+ <mat-error
+ *ngIf="siteAreaControl.get('label').hasError('required')"
+ >
+ {{ 'Site label is required' | translate }}
+ </mat-error>
</mat-form-field>
</sp-basic-field-description>
<sp-basic-field-description
@@ -53,6 +58,7 @@
fxFlex="100"
[locationConfig]="locationConfig"
[assetLocation]="site.location"
+ formControlName="location"
>
</sp-single-marker-map>
</sp-basic-field-description>
diff --git
a/ui/src/app/configuration/dialog/manage-site/edit-location/edit-location.component.ts
b/ui/src/app/configuration/dialog/manage-site/edit-location/edit-location.component.ts
index 9b1bc554c9..260adfc593 100644
---
a/ui/src/app/configuration/dialog/manage-site/edit-location/edit-location.component.ts
+++
b/ui/src/app/configuration/dialog/manage-site/edit-location/edit-location.component.ts
@@ -16,17 +16,29 @@
*
*/
-import { Component, Input } from '@angular/core';
+import { Component, Input, OnInit } from '@angular/core';
import { AssetSiteDesc, LocationConfig } from '@streampipes/platform-services';
+import { FormControl, FormGroup, Validators } from '@angular/forms';
@Component({
selector: 'sp-edit-asset-location-component',
templateUrl: './edit-location.component.html',
})
-export class EditAssetLocationComponent {
+export class EditAssetLocationComponent implements OnInit {
@Input()
site: AssetSiteDesc;
@Input()
locationConfig: LocationConfig;
+
+ siteAreaControl: FormGroup;
+
+ ngOnInit() {
+ this.siteAreaControl = new FormGroup({
+ label: new FormControl(this.site.label || '', [
+ Validators.required,
+ ]),
+ location: new FormControl(this.site.location || null, []),
+ });
+ }
}
diff --git
a/ui/src/app/configuration/dialog/manage-site/manage-site-dialog.component.html
b/ui/src/app/configuration/dialog/manage-site/manage-site-dialog.component.html
index 5cff55e084..47d8fa9b6d 100644
---
a/ui/src/app/configuration/dialog/manage-site/manage-site-dialog.component.html
+++
b/ui/src/app/configuration/dialog/manage-site/manage-site-dialog.component.html
@@ -20,6 +20,7 @@
<div class="sp-dialog-content p-15">
<div *ngIf="clonedSite !== undefined">
<sp-edit-asset-location-component
+ #editLocation
[site]="clonedSite"
[locationConfig]="locationConfig"
>
@@ -35,6 +36,7 @@
color="accent"
(click)="store()"
style="margin-right: 10px"
+ [disabled]="editLocationComponent?.siteAreaControl.invalid"
>
Save changes
</button>
diff --git
a/ui/src/app/configuration/dialog/manage-site/manage-site-dialog.component.ts
b/ui/src/app/configuration/dialog/manage-site/manage-site-dialog.component.ts
index 2a9b8b30a3..3846e7fd1b 100644
---
a/ui/src/app/configuration/dialog/manage-site/manage-site-dialog.component.ts
+++
b/ui/src/app/configuration/dialog/manage-site/manage-site-dialog.component.ts
@@ -16,7 +16,7 @@
*
*/
-import { Component, Input, OnInit } from '@angular/core';
+import { Component, Input, OnInit, ViewChild } from '@angular/core';
import { DialogRef } from '@streampipes/shared-ui';
import {
AssetConstants,
@@ -24,6 +24,7 @@ import {
GenericStorageService,
LocationConfig,
} from '@streampipes/platform-services';
+import { EditAssetLocationComponent } from
'./edit-location/edit-location.component';
@Component({
selector: 'sp-manage-site-dialog-component',
@@ -37,6 +38,9 @@ export class ManageSiteDialogComponent implements OnInit {
@Input()
locationConfig: LocationConfig;
+ @ViewChild('editLocation')
+ editLocationComponent: EditAssetLocationComponent;
+
clonedSite: AssetSiteDesc;
createMode = false;
@@ -61,7 +65,7 @@ export class ManageSiteDialogComponent implements OnInit {
this.clonedSite = {
appDocType: AssetConstants.ASSET_SITES_APP_DOC_NAME,
_id: undefined,
- label: 'New site',
+ label: '',
location: { coordinates: { latitude: 0, longitude: 0 } },
areas: [],
};
@@ -69,6 +73,11 @@ export class ManageSiteDialogComponent implements OnInit {
}
store(): void {
+ const formData = this.editLocationComponent?.siteAreaControl;
+ const { label, location } = formData.value;
+ this.clonedSite.label = label;
+ this.clonedSite.location = location;
+
const observable = this.createMode
? this.genericStorageService.createDocument(
AssetConstants.ASSET_SITES_APP_DOC_NAME,
diff --git
a/ui/src/app/core-ui/single-marker-map/single-marker-map.component.ts
b/ui/src/app/core-ui/single-marker-map/single-marker-map.component.ts
index 2336f51769..deb55ce41a 100644
--- a/ui/src/app/core-ui/single-marker-map/single-marker-map.component.ts
+++ b/ui/src/app/core-ui/single-marker-map/single-marker-map.component.ts
@@ -16,7 +16,7 @@
*
*/
-import { Component, Input, OnInit } from '@angular/core';
+import { Component, forwardRef, Input, OnInit } from '@angular/core';
import {
icon,
Layer,
@@ -32,12 +32,20 @@ import {
LatLng,
LocationConfig,
} from '@streampipes/platform-services';
+import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
@Component({
selector: 'sp-single-marker-map',
templateUrl: './single-marker-map.component.html',
+ providers: [
+ {
+ provide: NG_VALUE_ACCESSOR,
+ useExisting: forwardRef(() => SingleMarkerMapComponent),
+ multi: true,
+ },
+ ],
})
-export class SingleMarkerMapComponent implements OnInit {
+export class SingleMarkerMapComponent implements OnInit, ControlValueAccessor {
@Input()
locationConfig: LocationConfig;
@@ -101,6 +109,7 @@ export class SingleMarkerMapComponent implements OnInit {
onZoomChange(zoom: number): void {
this.assetLocation.zoom = zoom;
+ this.emitChange();
}
onMarkerAdded(e: LeafletMouseEvent) {
@@ -109,6 +118,7 @@ export class SingleMarkerMapComponent implements OnInit {
latitude: e.latlng.lat,
longitude: e.latlng.lng,
});
+ this.emitChange();
}
}
@@ -126,4 +136,37 @@ export class SingleMarkerMapComponent implements OnInit {
this.assetLocation.coordinates = location;
}
}
+
+ private onChange: (_: AssetLocation) => void = () => {};
+ private onTouched: () => void = () => {};
+
+ registerOnChange(fn: any): void {
+ this.onChange = fn;
+ }
+
+ registerOnTouched(fn: any): void {
+ this.onTouched = fn;
+ }
+
+ setDisabledState?(isDisabled: boolean): void {
+ this.readonly = isDisabled;
+ }
+
+ private emitChange() {
+ this.onChange(this.assetLocation);
+ this.onTouched();
+ }
+
+ writeValue(value: AssetLocation): void {
+ if (value) {
+ this.assetLocation = value;
+ if (this.map) {
+ this.addMarker(value.coordinates);
+ this.map.setView(
+ [value.coordinates.latitude, value.coordinates.longitude],
+ value.zoom || 1,
+ );
+ }
+ }
+ }
}
diff --git a/ui/src/app/core-ui/static-properties/input.validator.ts
b/ui/src/app/core-ui/static-properties/input.validator.ts
index 1d8e1f1c75..91e30b8744 100644
--- a/ui/src/app/core-ui/static-properties/input.validator.ts
+++ b/ui/src/app/core-ui/static-properties/input.validator.ts
@@ -50,6 +50,21 @@ export function ValidateString(control: AbstractControl) {
return null;
}
+export function checkForDuplicatesValidator(
+ getExistingNames: () => string[],
+): ValidatorFn {
+ return (control: AbstractControl): ValidationErrors | null => {
+ if (!control.value || typeof control.value !== 'string') {
+ return null;
+ }
+ const existingNames = getExistingNames();
+
+ const isDuplicate = existingNames.includes(control.value);
+
+ return isDuplicate ? { forbiddenName: { value: control.value } } :
null;
+ };
+}
+
export function ValidateName(): ValidatorFn {
return (control: AbstractControl): ValidationErrors | null => {
const value = control.value;