This is an automated email from the ASF dual-hosted git repository. mfholz pushed a commit to branch add-reactive-forms in repository https://gitbox.apache.org/repos/asf/streampipes.git
commit 7592ba63af09d95f02f84484e6540b9d404eee63 Author: Marcelfrueh <[email protected]> AuthorDate: Tue May 20 13:06:48 2025 +0200 feat: add reactive forms to Configuration/sites --- 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 | 17 +++++++- 12 files changed, 155 insertions(+), 17 deletions(-) diff --git a/ui/deployment/i18n/de.json b/ui/deployment/i18n/de.json index 5352dd885b..b1ef0972c8 100644 --- a/ui/deployment/i18n/de.json +++ b/ui/deployment/i18n/de.json @@ -287,6 +287,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 9264e48626..06efeb24a0 100644 --- a/ui/deployment/i18n/en.json +++ b/ui/deployment/i18n/en.json @@ -287,6 +287,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 ee493b944c..a728d4a52e 100644 --- a/ui/src/app/core-ui/static-properties/input.validator.ts +++ b/ui/src/app/core-ui/static-properties/input.validator.ts @@ -16,7 +16,7 @@ * */ -import { AbstractControl } from '@angular/forms'; +import { AbstractControl, ValidationErrors, ValidatorFn } from '@angular/forms'; export function ValidateUrl(control: AbstractControl) { if (control.value == null) { @@ -49,3 +49,18 @@ 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; + }; +}
