Skip to content

Commit 67d4ae7

Browse files
committed
fix create layer
1 parent 3bfd633 commit 67d4ae7

File tree

9 files changed

+64
-181
lines changed

9 files changed

+64
-181
lines changed

web-app/admin/src/app/admin/admin-layers/admin-layers.module.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,15 +11,13 @@ import { MatFormFieldModule } from '@angular/material/form-field';
1111
import { MatTooltipModule } from '@angular/material/tooltip';
1212
import { CoreModule } from '../../core/core.module';
1313
import { LayerDashboardComponent } from './dashboard/layer-dashboard.component';
14-
import { LayerDeleteDialogComponent } from './dashboard/layer-delete-dialog/layer-delete-dialog.component';
1514
import { CreateLayerDialogComponent } from './create-layer/create-layer.component';
1615
import { LayersService } from './layers.service';
1716
import { AdminBreadcrumbModule } from '../admin-breadcrumb/admin-breadcrumb.module';
1817

1918
@NgModule({
2019
declarations: [
2120
LayerDashboardComponent,
22-
LayerDeleteDialogComponent,
2321
CreateLayerDialogComponent
2422
],
2523
imports: [

web-app/admin/src/app/admin/admin-layers/create-layer/create-layer.component.html

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,18 @@ <h2 class="dialog-title">Create a New Layer</h2>
1414
<label class="field-label">Layer Name</label>
1515
<input type="text" class="form-input" formControlName="name" placeholder="Enter layer name" />
1616
<div class="field-error">
17-
<span class="error-text" [class.visible]="
18-
layerForm.get('name').invalid &&
17+
<span class="error-text visible" *ngIf="
18+
layerForm.get('name').hasError('required') &&
1919
(layerForm.get('name').touched || layerForm.get('name').dirty)
2020
">
2121
Name is required
2222
</span>
23+
<span class="error-text visible" *ngIf="
24+
layerForm.get('name').hasError('duplicateName') &&
25+
(layerForm.get('name').touched || layerForm.get('name').dirty)
26+
">
27+
A layer with this name already exists
28+
</span>
2329
</div>
2430
</div>
2531

web-app/admin/src/app/admin/admin-layers/create-layer/create-layer.component.ts

Lines changed: 55 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
import { Component, Inject } from '@angular/core';
22
import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog';
3-
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
3+
import { FormBuilder, FormGroup, Validators, AbstractControl, ValidationErrors, AsyncValidatorFn } from '@angular/forms';
44
import { LayersService, Layer } from '../layers.service';
5+
import { Observable, of } from 'rxjs';
6+
import { map, catchError, debounceTime, first } from 'rxjs/operators';
57

68
/**
79
* Dialog component for creating new layers.
@@ -25,16 +27,42 @@ export class CreateLayerDialogComponent {
2527
private layersService: LayersService
2628
) {
2729
this.layerForm = this.fb.group({
28-
name: [data.layer?.name ?? '', [Validators.required]],
30+
name: [
31+
data.layer?.name ?? '',
32+
[Validators.required],
33+
[this.duplicateLayerNameValidator()]
34+
],
2935
type: [data.layer?.type ?? '', [Validators.required]],
3036
description: [data.layer?.description ?? ''],
31-
// Imagery fields
3237
url: [''],
3338
format: ['XYZ'],
3439
base: [false]
3540
});
3641
}
3742

43+
/**
44+
* Async validator to check if a layer name already exists
45+
*/
46+
private duplicateLayerNameValidator(): AsyncValidatorFn {
47+
return (control: AbstractControl): Observable<ValidationErrors | null> => {
48+
if (!control.value) {
49+
return of(null);
50+
}
51+
52+
return this.layersService.getLayers().pipe(
53+
debounceTime(300),
54+
map(layers => {
55+
const nameExists = layers.some(
56+
layer => layer.name?.toLowerCase() === control.value.toLowerCase()
57+
);
58+
return nameExists ? { duplicateName: true } : null;
59+
}),
60+
catchError(() => of(null)),
61+
first()
62+
);
63+
};
64+
}
65+
3866
/**
3967
* Handles layer type change to add/remove validators and reset fields
4068
*/
@@ -82,27 +110,38 @@ export class CreateLayerDialogComponent {
82110
return;
83111
}
84112

85-
// Validate GeoPackage file
86113
if (this.layerForm.get('type')?.value === 'GeoPackage' && !this.geopackageFile) {
87114
this.errorMessage = 'Please select a GeoPackage file.';
88115
return;
89116
}
90117

91118
this.errorMessage = '';
92119
const formValue = this.layerForm.value;
93-
const layerData: any = {
94-
name: formValue.name,
95-
type: formValue.type,
96-
description: formValue.description
97-
};
98120

99-
// Add type-specific fields
100-
if (formValue.type === 'Imagery') {
101-
layerData.url = formValue.url;
102-
layerData.format = formValue.format;
103-
layerData.base = formValue.base;
104-
} else if (formValue.type === 'GeoPackage' && this.geopackageFile) {
105-
layerData.geopackage = this.geopackageFile;
121+
let layerData: any;
122+
123+
if (formValue.type === 'GeoPackage' && this.geopackageFile) {
124+
const formData = new FormData();
125+
formData.append('name', formValue.name);
126+
formData.append('type', formValue.type);
127+
if (formValue.description) {
128+
formData.append('description', formValue.description);
129+
}
130+
formData.append('geopackage', this.geopackageFile);
131+
layerData = formData;
132+
} else {
133+
// Use regular JSON for other layer types
134+
layerData = {
135+
name: formValue.name,
136+
type: formValue.type,
137+
description: formValue.description
138+
};
139+
140+
if (formValue.type === 'Imagery') {
141+
layerData.url = formValue.url;
142+
layerData.format = formValue.format;
143+
layerData.base = formValue.base;
144+
}
106145
}
107146

108147
this.layersService.createLayer(layerData).subscribe({

web-app/admin/src/app/admin/admin-layers/dashboard/layer-dashboard.component.spec.ts

Lines changed: 0 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -316,30 +316,6 @@ describe('LayerDashboardComponent', () => {
316316
});
317317
});
318318

319-
describe('navigation', () => {
320-
beforeEach(() => {
321-
fixture.detectChanges();
322-
});
323-
324-
it('should navigate to layer detail', () => {
325-
const layer = mockLayers[0];
326-
component.gotoLayer(layer);
327-
328-
expect(mockStateService.go).toHaveBeenCalledWith('admin.layer', { layerId: 1 });
329-
});
330-
331-
it('should navigate to edit layer', () => {
332-
const event = new Event('click');
333-
spyOn(event, 'stopPropagation');
334-
const layer = mockLayers[0];
335-
336-
component.editLayer(event, layer);
337-
338-
expect(event.stopPropagation).toHaveBeenCalled();
339-
expect(mockStateService.go).toHaveBeenCalledWith('admin.layerEdit', { layerId: 1 });
340-
});
341-
});
342-
343319
describe('layer creation', () => {
344320
beforeEach(() => {
345321
fixture.detectChanges();
@@ -384,59 +360,12 @@ describe('LayerDashboardComponent', () => {
384360
});
385361
});
386362

387-
describe('layer deletion', () => {
388-
beforeEach(() => {
389-
fixture.detectChanges();
390-
});
391-
392-
it('should open delete confirmation dialog', () => {
393-
const event = new Event('click');
394-
spyOn(event, 'stopPropagation');
395-
const mockDialogRef = {
396-
afterClosed: () => of(null)
397-
};
398-
mockDialog.open.and.returnValue(mockDialogRef as any);
399-
400-
component.deleteLayer(event, mockLayers[0]);
401-
402-
expect(event.stopPropagation).toHaveBeenCalled();
403-
expect(mockDialog.open).toHaveBeenCalled();
404-
});
405-
406-
it('should remove layer from list after deletion', () => {
407-
const event = new Event('click');
408-
const layerToDelete = mockLayers[0];
409-
const mockDialogRef = {
410-
afterClosed: () => of(layerToDelete)
411-
};
412-
mockDialog.open.and.returnValue(mockDialogRef as any);
413-
414-
component.deleteLayer(event, layerToDelete);
415-
416-
expect(component.layers.length).toBe(2);
417-
expect(component.layers.find(l => l.id === 1)).toBeUndefined();
418-
});
419-
420-
it('should not remove layer if deletion is cancelled', () => {
421-
const event = new Event('click');
422-
const mockDialogRef = {
423-
afterClosed: () => of(null)
424-
};
425-
mockDialog.open.and.returnValue(mockDialogRef as any);
426-
427-
component.deleteLayer(event, mockLayers[0]);
428-
429-
expect(component.layers.length).toBe(3);
430-
});
431-
});
432-
433363
describe('responsive layout', () => {
434364
beforeEach(() => {
435365
fixture.detectChanges();
436366
});
437367

438368
it('should update layout values on window resize', () => {
439-
// Mock window.innerWidth
440369
Object.defineProperty(window, 'innerWidth', {
441370
writable: true,
442371
configurable: true,

web-app/admin/src/app/admin/admin-layers/dashboard/layer-dashboard.component.ts

Lines changed: 0 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import { StateService } from '@uirouter/angular';
55
import { UserService } from 'admin/src/app/upgrade/ajs-upgraded-providers';
66
import { LayersService, Layer } from '../layers.service';
77
import { AdminBreadcrumb } from '../../admin-breadcrumb/admin-breadcrumb.model';
8-
import { LayerDeleteDialogComponent } from './layer-delete-dialog/layer-delete-dialog.component';
98
import { CreateLayerDialogComponent } from '../create-layer/create-layer.component';
109
import _ from 'underscore';
1110

@@ -77,13 +76,11 @@ export class LayerDashboardComponent implements OnInit {
7776
const term = this.layerSearch.trim().toLowerCase();
7877

7978
this.filteredLayers = this.layers.filter(layer => {
80-
// Apply search filter
8179
const matchesSearch = !term ||
8280
layer.name?.toLowerCase().includes(term) ||
8381
layer.description?.toLowerCase().includes(term) ||
8482
layer.url?.toLowerCase().includes(term);
8583

86-
// Apply type filter
8784
const matchesType = this.filterByType(layer);
8885

8986
return matchesSearch && matchesType;
@@ -182,29 +179,6 @@ export class LayerDashboardComponent implements OnInit {
182179
this.stateService.go('admin.layer', { layerId: layer.id });
183180
}
184181

185-
/** Navigate to edit layer */
186-
editLayer(event: Event, layer: Layer): void {
187-
event.stopPropagation();
188-
this.stateService.go('admin.layerEdit', { layerId: layer.id });
189-
}
190-
191-
/** Open delete confirmation dialog */
192-
deleteLayer(event: Event, layer: Layer): void {
193-
event.stopPropagation();
194-
195-
const dialogRef = this.modal.open(LayerDeleteDialogComponent, {
196-
width: '500px',
197-
data: { layer }
198-
});
199-
200-
dialogRef.afterClosed().subscribe((deletedLayer) => {
201-
if (deletedLayer) {
202-
this.layers = _.without(this.layers, deletedLayer);
203-
this.applyFilters();
204-
}
205-
});
206-
}
207-
208182
/** Update layout-related values on resize */
209183
@HostListener('window:resize')
210184
onResize(): void {

web-app/admin/src/app/admin/admin-layers/dashboard/layer-delete-dialog/layer-delete-dialog.component.html

Lines changed: 0 additions & 20 deletions
This file was deleted.

web-app/admin/src/app/admin/admin-layers/dashboard/layer-delete-dialog/layer-delete-dialog.component.scss

Whitespace-only changes.

web-app/admin/src/app/admin/admin-layers/dashboard/layer-delete-dialog/layer-delete-dialog.component.ts

Lines changed: 0 additions & 43 deletions
This file was deleted.

web-app/admin/src/app/admin/admin-layers/layers.service.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ export class LayersService {
6161
return this.http.put<Layer>(`/api/layers/${id}`, layer);
6262
}
6363

64-
createLayer(layer: Partial<Layer>): Observable<Layer> {
64+
createLayer(layer: Partial<Layer> | FormData): Observable<Layer> {
6565
return this.http.post<Layer>('/api/layers', layer);
6666
}
6767

0 commit comments

Comments
 (0)