From a65fde6b7fe651b0d54d70813a95942d983855a1 Mon Sep 17 00:00:00 2001 From: Matthijs Laan Date: Wed, 5 Nov 2025 16:57:13 +0100 Subject: [PATCH] HTM-1754: Add icon for editing the layer to TOC tree nodes --- .../assets/locale/messages.admin-core.de.xlf | 4 ++ .../assets/locale/messages.admin-core.en.xlf | 3 + .../assets/locale/messages.admin-core.nl.xlf | 4 ++ .../components/components.module.ts | 4 +- .../toc-component-config.component.css | 0 .../toc-component-config.component.html | 10 ++++ .../toc-component-config.component.ts | 60 +++++++++++++++++++ .../src/lib/models/component-config/index.ts | 1 + .../component-config/toc-config.model.ts | 5 ++ .../core/assets/locale/messages.core.de.xlf | 4 ++ .../core/assets/locale/messages.core.en.xlf | 3 + .../core/assets/locale/messages.core.nl.xlf | 4 ++ .../toc-node-layer.component.html | 9 +++ .../toc-node-layer.component.ts | 13 ++++ .../lib/components/toc/toc/toc.component.html | 5 +- .../components/toc/toc/toc.component.spec.ts | 5 +- .../lib/components/toc/toc/toc.component.ts | 34 ++++++++++- 17 files changed, 162 insertions(+), 6 deletions(-) create mode 100644 projects/admin-core/src/lib/application/components/toc-config/toc-component-config.component.css create mode 100644 projects/admin-core/src/lib/application/components/toc-config/toc-component-config.component.html create mode 100644 projects/admin-core/src/lib/application/components/toc-config/toc-component-config.component.ts create mode 100644 projects/api/src/lib/models/component-config/toc-config.model.ts diff --git a/projects/admin-core/assets/locale/messages.admin-core.de.xlf b/projects/admin-core/assets/locale/messages.admin-core.de.xlf index fd7834b58..b6b394e0d 100644 --- a/projects/admin-core/assets/locale/messages.admin-core.de.xlf +++ b/projects/admin-core/assets/locale/messages.admin-core.de.xlf @@ -1610,6 +1610,10 @@ Search title Suchtitel + + Show edit layer icon + Symbol zum Bearbeiten der Ebene anzeigen + Feature type settings updated Feature-Typ-Einstellungen aktualisiert diff --git a/projects/admin-core/assets/locale/messages.admin-core.en.xlf b/projects/admin-core/assets/locale/messages.admin-core.en.xlf index a95fcd349..7788a2edd 100644 --- a/projects/admin-core/assets/locale/messages.admin-core.en.xlf +++ b/projects/admin-core/assets/locale/messages.admin-core.en.xlf @@ -1187,6 +1187,9 @@ Search title + + Show edit layer icon + Feature type settings updated diff --git a/projects/admin-core/assets/locale/messages.admin-core.nl.xlf b/projects/admin-core/assets/locale/messages.admin-core.nl.xlf index 4aa79cf84..ded9ff2ba 100644 --- a/projects/admin-core/assets/locale/messages.admin-core.nl.xlf +++ b/projects/admin-core/assets/locale/messages.admin-core.nl.xlf @@ -1609,6 +1609,10 @@ Search title Zoeken titel + + Show edit layer icon + Toon icoon voor het bewerken van de laag + Feature type settings updated Feature type instellingen aangepast diff --git a/projects/admin-core/src/lib/application/components/components.module.ts b/projects/admin-core/src/lib/application/components/components.module.ts index 5b90ee54a..20dd2e3fb 100644 --- a/projects/admin-core/src/lib/application/components/components.module.ts +++ b/projects/admin-core/src/lib/application/components/components.module.ts @@ -16,6 +16,7 @@ import { EditComponentConfigComponent } from './edit-config/edit-component-confi import { GeolocationConfigComponent } from './geolocation-config/geolocation-config.component'; import { InfoConfigComponent } from './info-config/info-config.component'; import { DrawingConfigComponent } from './drawing-config/drawing-config.component'; +import { TocComponentConfigComponent } from './toc-config/toc-component-config.component'; @NgModule({ declarations: [ @@ -30,6 +31,7 @@ import { DrawingConfigComponent } from './drawing-config/drawing-config.componen GeolocationConfigComponent, InfoConfigComponent, DrawingConfigComponent, + TocComponentConfigComponent, ], imports: [ CommonModule, @@ -48,7 +50,7 @@ export class ComponentsModule { const configurationComponentService = inject(ConfigurationComponentRegistryService); /* eslint-disable max-len */ - configurationComponentService.registerConfigurationComponents(BaseComponentTypeEnum.TOC, $localize `:@@admin-core.application.component-table-of-contents:Table of contents`, BaseComponentConfigComponent); + configurationComponentService.registerConfigurationComponents(BaseComponentTypeEnum.TOC, $localize `:@@admin-core.application.component-table-of-contents:Table of contents`, TocComponentConfigComponent); configurationComponentService.registerConfigurationComponents(BaseComponentTypeEnum.LEGEND, $localize `:@@admin-core.application.component-legend:Legend`, BaseComponentConfigComponent); configurationComponentService.registerConfigurationComponents(BaseComponentTypeEnum.DRAWING, $localize `:@@admin-core.application.component-drawing:Drawing`, DrawingConfigComponent); configurationComponentService.registerConfigurationComponents(BaseComponentTypeEnum.PRINT, $localize `:@@admin-core.application.component-print:Print`, BaseComponentConfigComponent); diff --git a/projects/admin-core/src/lib/application/components/toc-config/toc-component-config.component.css b/projects/admin-core/src/lib/application/components/toc-config/toc-component-config.component.css new file mode 100644 index 000000000..e69de29bb diff --git a/projects/admin-core/src/lib/application/components/toc-config/toc-component-config.component.html b/projects/admin-core/src/lib/application/components/toc-config/toc-component-config.component.html new file mode 100644 index 000000000..674ec6f01 --- /dev/null +++ b/projects/admin-core/src/lib/application/components/toc-config/toc-component-config.component.html @@ -0,0 +1,10 @@ + +
+ Show edit layer icon +
+
diff --git a/projects/admin-core/src/lib/application/components/toc-config/toc-component-config.component.ts b/projects/admin-core/src/lib/application/components/toc-config/toc-component-config.component.ts new file mode 100644 index 000000000..8522e03ff --- /dev/null +++ b/projects/admin-core/src/lib/application/components/toc-config/toc-component-config.component.ts @@ -0,0 +1,60 @@ +import { ChangeDetectionStrategy, Component, DestroyRef, Input, inject } from '@angular/core'; +import { + BaseComponentTypeEnum, TocConfigModel, +} from '@tailormap-viewer/api'; +import { FormControl, FormGroup } from '@angular/forms'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; +import { ComponentConfigurationService } from '../../services/component-configuration.service'; +import { ConfigurationComponentModel } from '../configuration-component.model'; +import { debounceTime } from 'rxjs'; + +@Component({ + selector: 'tm-admin-edit-component-config', + templateUrl: './toc-component-config.component.html', + styleUrls: ['./toc-component-config.component.css'], + changeDetection: ChangeDetectionStrategy.OnPush, + standalone: false, +}) +export class TocComponentConfigComponent implements ConfigurationComponentModel { + private componentConfigService = inject(ComponentConfigurationService); + private destroyRef = inject(DestroyRef); + + @Input() + public type: BaseComponentTypeEnum | undefined; + + @Input() + public label: string | undefined; + + @Input() + public set config(config: TocConfigModel | undefined) { + this._config = config; + this.initForm(config); + } + public get config() { + return this._config; + } + private _config: TocConfigModel | undefined; + + public formGroup = new FormGroup({ + showEditLayerIcon: new FormControl(false), + }); + + constructor() { + this.formGroup.valueChanges + .pipe(takeUntilDestroyed(this.destroyRef), debounceTime(250)) + .subscribe(() => { + if (!this.formGroup.valid) { + return; + } + this.saveConfig(); + }); + } + + public initForm(config: TocConfigModel | undefined) { + this.formGroup.patchValue({ showEditLayerIcon: config?.showEditLayerIcon ?? false }, { emitEvent: false }); + } + + private saveConfig() { + this.componentConfigService.updateConfigForKey(this.type, 'showEditLayerIcon', this.formGroup.value.showEditLayerIcon); + } +} diff --git a/projects/api/src/lib/models/component-config/index.ts b/projects/api/src/lib/models/component-config/index.ts index 3059481bc..778dc845e 100644 --- a/projects/api/src/lib/models/component-config/index.ts +++ b/projects/api/src/lib/models/component-config/index.ts @@ -7,3 +7,4 @@ export * from './edit-config.model'; export * from './geolocation-config.model'; export * from './info-component-config.model'; export * from './drawing-component-config.model'; +export * from './toc-config.model'; diff --git a/projects/api/src/lib/models/component-config/toc-config.model.ts b/projects/api/src/lib/models/component-config/toc-config.model.ts new file mode 100644 index 000000000..fdbc53854 --- /dev/null +++ b/projects/api/src/lib/models/component-config/toc-config.model.ts @@ -0,0 +1,5 @@ +import { ComponentBaseConfigModel } from '../component-base-config.model'; + +export interface TocConfigModel extends ComponentBaseConfigModel { + showEditLayerIcon?: boolean; +} diff --git a/projects/core/assets/locale/messages.core.de.xlf b/projects/core/assets/locale/messages.core.de.xlf index 4a0c35998..ae107d8ee 100644 --- a/projects/core/assets/locale/messages.core.de.xlf +++ b/projects/core/assets/locale/messages.core.de.xlf @@ -1126,6 +1126,10 @@ Details for Details für
+ + Edit layer + Ebene bearbeiten + Filter by layer name... Nach Ebenennamen filtern... diff --git a/projects/core/assets/locale/messages.core.en.xlf b/projects/core/assets/locale/messages.core.en.xlf index 03d703323..d1d4be459 100644 --- a/projects/core/assets/locale/messages.core.en.xlf +++ b/projects/core/assets/locale/messages.core.en.xlf @@ -845,6 +845,9 @@ Details for + + Edit layer + Filter by layer name... diff --git a/projects/core/assets/locale/messages.core.nl.xlf b/projects/core/assets/locale/messages.core.nl.xlf index de6a022e0..c8095d022 100644 --- a/projects/core/assets/locale/messages.core.nl.xlf +++ b/projects/core/assets/locale/messages.core.nl.xlf @@ -1126,6 +1126,10 @@ Details for Details voor + + Edit layer + Laag bewerken + Filter by layer name... Filter op laagnaam... diff --git a/projects/core/src/lib/components/toc/toc-node-layer/toc-node-layer.component.html b/projects/core/src/lib/components/toc/toc-node-layer/toc-node-layer.component.html index 2cc2610cd..a1d0aab30 100644 --- a/projects/core/src/lib/components/toc/toc-node-layer/toc-node-layer.component.html +++ b/projects/core/src/lib/components/toc/toc-node-layer/toc-node-layer.component.html @@ -31,5 +31,14 @@ matListItemMeta> } + @if (isInScale() && isLayerEditable() && node.checked) { + + + } } diff --git a/projects/core/src/lib/components/toc/toc-node-layer/toc-node-layer.component.ts b/projects/core/src/lib/components/toc/toc-node-layer/toc-node-layer.component.ts index f65b284a7..7da79fa55 100644 --- a/projects/core/src/lib/components/toc/toc-node-layer/toc-node-layer.component.ts +++ b/projects/core/src/lib/components/toc/toc-node-layer/toc-node-layer.component.ts @@ -29,9 +29,15 @@ export class TocNodeLayerComponent { @Input() public filteredLayerIds: string[] = []; + @Input() + public editableLayerIds: string[] = []; + @Output() public zoomToScale = new EventEmitter(); + @Output() + public editLayer = new EventEmitter(); + public isLevel() { return this.node?.type === 'level'; } @@ -56,6 +62,10 @@ export class TocNodeLayerComponent { return this.filteredLayerIds.includes(this.node?.id || ''); } + public isLayerEditable() { + return this.editableLayerIds.includes(this.node?.id || ''); + } + public zoomToLayer($event: MouseEvent, node: TreeModel) { $event.stopPropagation(); const scales: number[] = []; @@ -72,4 +82,7 @@ export class TocNodeLayerComponent { this.zoomToScale.emit(zoomToScale); } + public editLayerClicked(node: TreeModel) { + this.editLayer.emit(node.id); + } } diff --git a/projects/core/src/lib/components/toc/toc/toc.component.html b/projects/core/src/lib/components/toc/toc/toc.component.html index 7e731a222..9f6b379c2 100644 --- a/projects/core/src/lib/components/toc/toc/toc.component.html +++ b/projects/core/src/lib/components/toc/toc/toc.component.html @@ -6,7 +6,10 @@ [layersWithoutWebMercator]="layersWithoutWebMercator()" [tiles3DLayerIds]="tiles3DLayerIds()" [filteredLayerIds]="filteredLayerIds()" - (zoomToScale)="zoomToScale($event)"> + [editableLayerIds]="config?.showEditLayerIcon && editComponentEnabled ? editableLayerIds() : []" + (zoomToScale)="zoomToScale($event)" + (editLayer)="editLayer($event)" + > @if (visible$ | async) { diff --git a/projects/core/src/lib/components/toc/toc/toc.component.spec.ts b/projects/core/src/lib/components/toc/toc/toc.component.spec.ts index 2b038cbcd..bcf1bbfce 100644 --- a/projects/core/src/lib/components/toc/toc/toc.component.spec.ts +++ b/projects/core/src/lib/components/toc/toc/toc.component.spec.ts @@ -3,7 +3,7 @@ import { render, screen, waitFor } from '@testing-library/angular'; import { createMockStore } from '@ngrx/store/testing'; import { MenubarService } from '../../menubar'; import { of } from 'rxjs'; -import { SharedModule } from '@tailormap-viewer/shared'; +import { LoadingStateEnum, SharedModule } from '@tailormap-viewer/shared'; import userEvent from '@testing-library/user-event'; import { MatIconTestingModule } from '@angular/material/icon/testing'; import { @@ -21,6 +21,7 @@ import { Store } from '@ngrx/store'; import { TocNodeDetailsComponent } from '../toc-node-details/toc-node-details.component'; import { getMapServiceMock } from '../../../test-helpers/map-service.mock.spec'; import { selectFilteredLayerIds } from '../../../state/filter-state/filter.selectors'; +import { selectComponentsConfig, selectViewerLoadingState } from '../../../state'; const buildMockStore = (selectedLayer = '') => { const layers = [ @@ -44,6 +45,8 @@ const buildMockStore = (selectedLayer = '') => { { selector: selectLayersWithoutWebMercatorIds, value: [] }, { selector: select3dTilesLayers, value: [] }, { selector: selectFilteredLayerIds, value: [] }, + { selector: selectViewerLoadingState, value: LoadingStateEnum.LOADED }, + { selector: selectComponentsConfig, value: [] }, ], }); }; diff --git a/projects/core/src/lib/components/toc/toc/toc.component.ts b/projects/core/src/lib/components/toc/toc/toc.component.ts index 096f04957..af5fa502a 100644 --- a/projects/core/src/lib/components/toc/toc/toc.component.ts +++ b/projects/core/src/lib/components/toc/toc/toc.component.ts @@ -8,15 +8,17 @@ import { map, tap } from 'rxjs/operators'; import { MenubarService } from '../../menubar'; import { TocMenuButtonComponent } from '../toc-menu-button/toc-menu-button.component'; import { Store } from '@ngrx/store'; -import { AppLayerModel, BaseComponentTypeEnum } from '@tailormap-viewer/api'; +import { AppLayerModel, BaseComponentConfigHelper, BaseComponentTypeEnum, EditConfigModel, TocConfigModel } from '@tailormap-viewer/api'; import { MapService } from '@tailormap-viewer/map'; import { selectFilteredLayerTree, selectFilterEnabled } from '../state/toc.selectors'; import { toggleFilterEnabled } from '../state/toc.actions'; import { - select3dTilesLayers, selectIn3dView, selectLayersWithoutWebMercatorIds, selectSelectedNode, selectSelectedNodeId, + select3dTilesLayers, selectEditableLayers, selectIn3dView, selectLayersWithoutWebMercatorIds, selectSelectedNode, selectSelectedNodeId, } from '../../../map/state/map.selectors'; import { moveLayerTreeNode, setLayerVisibility, toggleSelectedLayerId, toggleLevelExpansion } from '../../../map/state/map.actions'; import { selectFilteredLayerIds } from '../../../state/filter-state/filter.selectors'; +import { setEditActive, setSelectedEditLayer } from '../../edit/state/edit.actions'; +import { ComponentConfigHelper } from '../../../shared'; interface AppLayerTreeModel extends BaseTreeModel { metadata: AppLayerModel; @@ -38,7 +40,6 @@ export class TocComponent implements OnInit, OnDestroy { private mapService = inject(MapService); private ngZone = inject(NgZone); - private destroyed = new Subject(); public visible$: Observable = of(false); public scale: number | null = null; @@ -54,6 +55,11 @@ export class TocComponent implements OnInit, OnDestroy { public layersWithoutWebMercator: Signal = signal([]); public tiles3DLayerIds: Signal = signal([]); public filteredLayerIds: Signal = signal([]); + public editableLayerIds: Signal = signal([]); + + public config: TocConfigModel | undefined; + + public editComponentEnabled = !BaseComponentConfigHelper.isComponentDisabledByDefault(BaseComponentTypeEnum.EDIT); public ngOnInit(): void { this.visible$ = this.menubarService.isComponentVisible$(BaseComponentTypeEnum.TOC); @@ -94,11 +100,29 @@ export class TocComponent implements OnInit, OnDestroy { this.menubarService.registerComponent({ type: BaseComponentTypeEnum.TOC, component: TocMenuButtonComponent }); + ComponentConfigHelper.useInitialConfigForComponent( + this.store$, + BaseComponentTypeEnum.TOC, + config => { + this.config = config; + }, + ); + + ComponentConfigHelper.useInitialConfigForComponent( + this.store$, + BaseComponentTypeEnum.EDIT, + config => { + this.editComponentEnabled = config.enabled; + }, + ); + this.in3D = this.store$.selectSignal(selectIn3dView); this.layersWithoutWebMercator = this.store$.selectSignal(selectLayersWithoutWebMercatorIds); const tiles3DLayers = this.store$.selectSignal(select3dTilesLayers); this.tiles3DLayerIds = computed(() => tiles3DLayers().map(l => l.id)); this.filteredLayerIds = this.store$.selectSignal(selectFilteredLayerIds); + const editableLayers = this.store$.selectSignal(selectEditableLayers); + this.editableLayerIds = computed(() => editableLayers().map(layer => layer.id)); } public getDropZoneConfig() { @@ -139,4 +163,8 @@ export class TocComponent implements OnInit, OnDestroy { this.mapService.zoomToScale(minScale); } + public editLayer(layer: string) { + this.store$.dispatch(setSelectedEditLayer( { layer })); + this.store$.dispatch(setEditActive({ active: true })); + } }