Skip to content

Commit 437808b

Browse files
authored
Edit: copy features from other layer (#1045)
2 parents 147976c + bed32e2 commit 437808b

30 files changed

+598
-127
lines changed

projects/admin-core/assets/locale/messages.admin-core.de.xlf

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,10 @@
150150
<source>Copy application <x id="INTERPOLATION" equiv-text="{{data.application.title}}"/></source>
151151
<target>Anwendung kopieren <x id="INTERPOLATION" equiv-text="{{data.application.title}}"/></target>
152152
</trans-unit>
153+
<trans-unit id="admin-core.application.copy-layers-info" datatype="html">
154+
<source>Select one or more layers that are available for copying features from. If no layers are selected, all layers will be available. Note that incompatible layer geometry types may prevent successful copying.</source>
155+
<target>Wählen Sie eine oder mehrere Ebenen aus, von denen Features kopiert werden können. Wenn keine Ebenen ausgewählt sind, stehen alle Ebenen zur Verfügung. Beachten Sie, dass inkompatible Geometrietypen der Ebenen ein erfolgreiches Kopieren verhindern können.</target>
156+
</trans-unit>
153157
<trans-unit id="admin-core.application.create-application" datatype="html">
154158
<source>Create application</source>
155159
<target>Anwendung erstellen</target>

projects/admin-core/assets/locale/messages.admin-core.en.xlf

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,9 @@
113113
<trans-unit id="admin-core.application.copy-application" datatype="html">
114114
<source>Copy application <x id="INTERPOLATION" equiv-text="{{data.application.title}}"/></source>
115115
</trans-unit>
116+
<trans-unit id="admin-core.application.copy-layers-info" datatype="html">
117+
<source>Select one or more layers that are available for copying features from. If no layers are selected, all layers will be available. Note that incompatible layer geometry types may prevent successful copying.</source>
118+
</trans-unit>
116119
<trans-unit id="admin-core.application.create-application" datatype="html">
117120
<source>Create application</source>
118121
</trans-unit>

projects/admin-core/assets/locale/messages.admin-core.nl.xlf

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,10 @@
150150
<source>Copy application <x id="INTERPOLATION" equiv-text="{{data.application.title}}"/></source>
151151
<target>Kopieer applicatie <x id="INTERPOLATION" equiv-text="{{data.application.title}}"/></target>
152152
</trans-unit>
153+
<trans-unit id="admin-core.application.copy-layers-info" datatype="html">
154+
<source>Select one or more layers that are available for copying features from. If no layers are selected, all layers will be available. Note that incompatible layer geometry types may prevent successful copying.</source>
155+
<target>Selecteer één of meerdere lagen waarvan objecten kunnen worden gekopiëerd. Als geen lagen zijn aangevinkt is dit mogelijk van alle lagen. Let op dat door incompatibele geometrie-types een object mogelijk niet gekopiëerd kan worden.</target>
156+
</trans-unit>
153157
<trans-unit id="admin-core.application.create-application" datatype="html">
154158
<source>Create application</source>
155159
<target>Applicatie aanmaken</target>

projects/admin-core/src/lib/application/components/components.module.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import { GeolocationConfigComponent } from './geolocation-config/geolocation-con
1717
import { InfoConfigComponent } from './info-config/info-config.component';
1818
import { DrawingConfigComponent } from './drawing-config/drawing-config.component';
1919
import { TocComponentConfigComponent } from './toc-config/toc-component-config.component';
20+
import { SharedAdminComponentsModule } from '../../shared/components';
2021

2122
@NgModule({
2223
declarations: [
@@ -39,6 +40,7 @@ import { TocComponentConfigComponent } from './toc-config/toc-component-config.c
3940
BaseComponentConfigComponent,
4041
SelectUploadModule,
4142
MarkdownEditorComponent,
43+
SharedAdminComponentsModule,
4244
],
4345
exports: [
4446
ComponentsListComponent,
@@ -50,7 +52,7 @@ export class ComponentsModule {
5052
const configurationComponentService = inject(ConfigurationComponentRegistryService);
5153

5254
/* eslint-disable max-len */
53-
configurationComponentService.registerConfigurationComponents(BaseComponentTypeEnum.TOC, $localize `:@@admin-core.application.component-table-of-contents:Table of contents`, TocComponentConfigComponent);
55+
configurationComponentService.registerConfigurationComponents(BaseComponentTypeEnum.TOC, $localize `:@@admin-core.application.component-table-of-contents:Table of contents`, BaseComponentConfigComponent);
5456
configurationComponentService.registerConfigurationComponents(BaseComponentTypeEnum.LEGEND, $localize `:@@admin-core.application.component-legend:Legend`, BaseComponentConfigComponent);
5557
configurationComponentService.registerConfigurationComponents(BaseComponentTypeEnum.DRAWING, $localize `:@@admin-core.application.component-drawing:Drawing`, DrawingConfigComponent);
5658
configurationComponentService.registerConfigurationComponents(BaseComponentTypeEnum.PRINT, $localize `:@@admin-core.application.component-print:Print`, BaseComponentConfigComponent);
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
div {
2+
margin-bottom: 8px;
3+
}
4+
5+
.copy-layers {
6+
max-width: 500px;
7+
}

projects/admin-core/src/lib/application/components/edit-config/edit-component-config.component.html

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,5 +6,22 @@
66
<div [formGroup]="formGroup">
77
<mat-checkbox formControlName="closeAfterAddFeature"
88
i18n="@@admin-core.components.edit-close-after-add-feature">Close window after adding new feature</mat-checkbox>
9+
10+
<div class="copy-layers">
11+
<div class="copy-layers-info">
12+
<span i18n="@@admin-core.application.copy-layers-info">Select one or more layers that are available for copying features from. If no layers are selected, all layers will be available. Note that incompatible layer geometry types may prevent successful copying.</span>
13+
</div>
14+
<tm-admin-list-filter [formControl]="copyLayerFilter"
15+
[filterTerm]="copyLayerFilterSignal()"
16+
i18n-label="@@admin-core.application.filter-tree"
17+
label="Filter application layers"></tm-admin-list-filter>
18+
<mat-selection-list (selectionChange)="onCopyLayerSelectionChange($event)">
19+
@for (layer of filteredLayerList(); track layer.id) {
20+
<mat-list-option [value]="layer.id" [selected]="layer.selected">
21+
<div class="layer-label">{{ layer.label }}</div>
22+
</mat-list-option>
23+
}
24+
</mat-selection-list>
25+
</div>
926
</div>
1027
</tm-admin-base-component-config>

projects/admin-core/src/lib/application/components/edit-config/edit-component-config.component.ts

Lines changed: 62 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,18 @@
1-
import { ChangeDetectionStrategy, Component, DestroyRef, Input, inject } from '@angular/core';
1+
import { ChangeDetectionStrategy, Component, DestroyRef, Input, inject, signal, OnInit, computed, effect } from '@angular/core';
22
import {
33
BaseComponentTypeEnum, EditConfigModel,
44
} from '@tailormap-viewer/api';
55
import { FormControl, FormGroup } from '@angular/forms';
66
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
77
import { ComponentConfigurationService } from '../../services/component-configuration.service';
88
import { ConfigurationComponentModel } from '../configuration-component.model';
9-
import { debounceTime } from 'rxjs';
9+
import { debounceTime, take } from 'rxjs';
10+
import { MatSelectionListChange } from '@angular/material/list';
11+
import { selectExtendedAppLayerNodesForSelectedApplication } from '../../state/application.selectors';
12+
import { Store } from '@ngrx/store';
13+
import { FilterHelper, LoadingStateEnum } from '@tailormap-viewer/shared';
14+
import { selectCatalogLoadStatus } from '../../../catalog/state/catalog.selectors';
15+
import { loadCatalog } from '../../../catalog/state/catalog.actions';
1016

1117
@Component({
1218
selector: 'tm-admin-edit-component-config',
@@ -15,10 +21,10 @@ import { debounceTime } from 'rxjs';
1521
changeDetection: ChangeDetectionStrategy.OnPush,
1622
standalone: false,
1723
})
18-
export class EditComponentConfigComponent implements ConfigurationComponentModel<EditConfigModel> {
24+
export class EditComponentConfigComponent implements ConfigurationComponentModel<EditConfigModel>, OnInit {
1925
private componentConfigService = inject(ComponentConfigurationService);
2026
private destroyRef = inject(DestroyRef);
21-
27+
private store$ = inject(Store);
2228

2329
@Input()
2430
public type: BaseComponentTypeEnum | undefined;
@@ -39,8 +45,36 @@ export class EditComponentConfigComponent implements ConfigurationComponentModel
3945
public formGroup = new FormGroup({
4046
closeAfterAddFeature: new FormControl<boolean>(false),
4147
});
48+
public selectedCopyLayers = signal<string[]>([]);
49+
50+
// Not in formGroup for config properties, used for layer filter control
51+
public copyLayerFilter = new FormControl<string>('');
52+
public copyLayerFilterSignal = signal<string>('');
53+
public allLayers = this.store$.selectSignal(selectExtendedAppLayerNodesForSelectedApplication);
54+
55+
public filteredLayerList = computed(() => {
56+
const allLayers = this.allLayers();
57+
const selectedLayerIds = this.selectedCopyLayers();
58+
const filterTerm = this.copyLayerFilterSignal();
59+
const layersWithSelected = allLayers.map(layer => ({
60+
...layer,
61+
selected: selectedLayerIds.includes(layer.id),
62+
}));
63+
if (filterTerm) {
64+
return FilterHelper.filterByTerm(layersWithSelected, filterTerm, l => l.label);
65+
}
66+
return layersWithSelected;
67+
});
4268

4369
constructor() {
70+
this.store$.select(selectCatalogLoadStatus)
71+
.pipe(take(1))
72+
.subscribe(loadStatus => {
73+
if (loadStatus === LoadingStateEnum.INITIAL || loadStatus === LoadingStateEnum.FAILED) {
74+
this.store$.dispatch(loadCatalog());
75+
}
76+
});
77+
4478
this.formGroup.valueChanges
4579
.pipe(takeUntilDestroyed(this.destroyRef), debounceTime(250))
4680
.subscribe(() => {
@@ -49,14 +83,38 @@ export class EditComponentConfigComponent implements ConfigurationComponentModel
4983
}
5084
this.saveConfig();
5185
});
86+
87+
effect(() => {
88+
this.componentConfigService.updateConfigForKey<EditConfigModel>(this.type, 'copyLayerIds', this.selectedCopyLayers());
89+
});
90+
}
91+
92+
public ngOnInit(): void {
93+
this.copyLayerFilter.valueChanges
94+
.pipe(takeUntilDestroyed(this.destroyRef))
95+
.subscribe(filterTerm => {
96+
this.copyLayerFilterSignal.set(filterTerm || '');
97+
});
5298
}
5399

54100
public initForm(config: EditConfigModel | undefined) {
55101
this.formGroup.patchValue({ closeAfterAddFeature: config?.closeAfterAddFeature ?? false }, { emitEvent: false });
102+
this.selectedCopyLayers.set(config?.copyLayerIds ?? []);
56103
}
57104

58105
private saveConfig() {
59106
this.componentConfigService.updateConfigForKey<EditConfigModel>(this.type, 'closeAfterAddFeature', this.formGroup.value.closeAfterAddFeature);
60107
}
61108

109+
public onCopyLayerSelectionChange($event: MatSelectionListChange) {
110+
const selectedLayers = [...this.selectedCopyLayers()];
111+
$event.options.forEach(option => {
112+
if (option.selected) {
113+
selectedLayers.push(option.value);
114+
} else {
115+
selectedLayers.splice(selectedLayers.indexOf(option.value), 1);
116+
}
117+
});
118+
this.selectedCopyLayers.set(selectedLayers);
119+
}
62120
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { ExtendedGeoServiceModel } from '../../catalog/models/extended-geo-service.model';
2+
import { ExtendedFeatureTypeModel } from '../../catalog/models/extended-feature-type.model';
3+
import { ExtendedGeoServiceLayerModel } from '../../catalog/models/extended-geo-service-layer.model';
4+
import { AppLayerSettingsModel, AppTreeLayerNodeModel } from '@tailormap-admin/admin-api';
5+
6+
export interface ExtendedAppTreeLayerNodeModel extends AppTreeLayerNodeModel {
7+
label: string;
8+
appLayerSettings: AppLayerSettingsModel;
9+
geoService: ExtendedGeoServiceModel | undefined;
10+
geoServiceLayer: ExtendedGeoServiceLayerModel | undefined;
11+
featureType: ExtendedFeatureTypeModel | undefined;
12+
}

projects/admin-core/src/lib/application/state/application.selectors.ts

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import { ApplicationModelHelper } from '../helpers/application-model.helper';
1111
import { GeoServiceLayerInApplicationModel } from '../models/geo-service-layer-in-application.model';
1212
import { ExtendedGeoServiceLayerModel } from '../../catalog/models/extended-geo-service-layer.model';
1313
import { ExtendedFilterGroupModel } from '../models/extended-filter-group.model';
14+
import { ExtendedAppTreeLayerNodeModel } from '../models/extended-app-tree-layer-node.model';
1415

1516
const selectApplicationState = createFeatureSelector<ApplicationState>(applicationStateKey);
1617

@@ -205,6 +206,36 @@ export const selectStylingConfig = createSelector(selectDraftApplication, applic
205206

206207
export const selectFilterGroups = createSelector(selectDraftApplication, application => application?.settings?.filterGroups || []);
207208

209+
export const selectExtendedAppLayerNodesForSelectedApplication = createSelector(
210+
selectAppLayerNodesForSelectedApplication,
211+
selectGeoServices,
212+
selectGeoServiceLayers,
213+
selectSelectedApplicationLayerSettings,
214+
selectFeatureTypes,
215+
(appLayerTreeNodes, geoServices, geoServiceLayers, layerSettings, featureTypes) => {
216+
const geoServiceLayerMap = ApplicationTreeHelper.getLayerMap(geoServiceLayers);
217+
return appLayerTreeNodes
218+
.filter(node => ApplicationModelHelper.isLayerTreeNode(node))
219+
.map((appLayerNode): ExtendedAppTreeLayerNodeModel => {
220+
const geoServiceLayer = geoServiceLayerMap.get(ApplicationTreeHelper.getLayerMapKey(appLayerNode.layerName, appLayerNode.serviceId));
221+
const geoService = geoServices.find(service => service.id === geoServiceLayer?.serviceId);
222+
const appLayerSettings = layerSettings[appLayerNode.id];
223+
const featureType = featureTypes.find(ft => {
224+
return ft.featureSourceId === geoServiceLayer?.layerSettings?.featureType?.featureSourceId.toString()
225+
&& ft.name === geoServiceLayer.layerSettings?.featureType?.featureTypeName;
226+
});
227+
return {
228+
...appLayerNode,
229+
label: ApplicationTreeHelper.getTreeModelLabel(appLayerNode, geoServiceLayerMap, layerSettings, 'layer'),
230+
appLayerSettings,
231+
geoService,
232+
geoServiceLayer,
233+
featureType,
234+
};
235+
});
236+
},
237+
);
238+
208239
export const selectFilterableLayersForApplication = createSelector(
209240
selectAppLayerNodesForSelectedApplication,
210241
selectGeoServiceLayers,

projects/api/src/lib/models/component-config/edit-config.model.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,5 @@ import { ComponentBaseConfigModel } from '../component-base-config.model';
22

33
export interface EditConfigModel extends ComponentBaseConfigModel {
44
closeAfterAddFeature: boolean;
5+
copyLayerIds?: string[];
56
}

0 commit comments

Comments
 (0)