@regionerne/gis-komponent 0.0.4 → 0.0.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,20 +1,22 @@
1
+ import * as i1 from '@angular/common';
1
2
  import { CommonModule } from '@angular/common';
2
3
  import * as i0 from '@angular/core';
3
- import { InjectionToken, inject, Injectable, Input, ViewChild, Component } from '@angular/core';
4
+ import { InjectionToken, inject, Injectable, Component, Input, ViewChild } from '@angular/core';
4
5
  import * as i2 from '@angular/material/icon';
5
6
  import { MatIconModule } from '@angular/material/icon';
6
- import Map from 'ol/Map';
7
+ import Map$1 from 'ol/Map';
7
8
  import View from 'ol/View';
8
9
  import TileLayer from 'ol/layer/Tile';
9
- import ol_layer_Vector from 'ol/layer/Vector';
10
+ import VectorLayer from 'ol/layer/Vector';
10
11
  import VectorSource from 'ol/source/Vector';
11
12
  import ImageWMS from 'ol/source/ImageWMS';
12
13
  import WMTSCapabilities from 'ol/format/WMTSCapabilities';
13
14
  import Draw from 'ol/interaction/Draw';
14
15
  import Translate from 'ol/interaction/Translate';
15
16
  import ImageLayer from 'ol/layer/Image';
16
- import { HttpClient, HttpHeaders } from '@angular/common/http';
17
+ import { HttpClient, HttpErrorResponse, HttpHeaders, provideHttpClient, withInterceptors } from '@angular/common/http';
17
18
  import OLLayerGroup from 'ol/layer/Group';
19
+ import * as i4$1 from '@angular/material/select';
18
20
  import { MatSelectModule } from '@angular/material/select';
19
21
  import * as i2$1 from '@angular/material/list';
20
22
  import { MatListModule } from '@angular/material/list';
@@ -24,23 +26,41 @@ import { register } from 'ol/proj/proj4';
24
26
  import proj4 from 'proj4';
25
27
  import { Control } from 'ol/control';
26
28
  import { TileWMS } from 'ol/source';
27
- import { map, of, combineLatest } from 'rxjs';
29
+ import { Subject, of, tap, map, filter, combineLatest, catchError, throwError } from 'rxjs';
28
30
  import WMTS, { optionsFromCapabilities } from 'ol/source/WMTS';
29
31
  import * as i4 from '@angular/cdk/drag-drop';
30
32
  import { moveItemInArray, transferArrayItem, DragDropModule } from '@angular/cdk/drag-drop';
31
- import * as i1 from '@angular/material/form-field';
33
+ import * as i3 from '@angular/material/form-field';
32
34
  import { MatFormFieldModule } from '@angular/material/form-field';
33
- import * as i3 from '@angular/forms';
35
+ import * as i3$1 from '@angular/forms';
34
36
  import { FormsModule } from '@angular/forms';
35
37
  import * as i5 from '@angular/material/expansion';
36
38
  import { MatExpansionModule } from '@angular/material/expansion';
37
39
  import * as i6 from '@angular/material/input';
38
40
  import { MatInputModule } from '@angular/material/input';
39
41
  import ol_control_Control from 'ol/control/Control';
40
- import WKT from 'ol/format/WKT';
42
+ import TileSource from 'ol/source/Tile';
43
+ import ImageSource from 'ol/source/Image';
44
+ import { unByKey } from 'ol/Observable';
45
+ import { MAT_SNACK_BAR_DATA, MatSnackBarRef, MatSnackBar } from '@angular/material/snack-bar';
41
46
  import * as SLDReader from '@nieuwlandgeo/sldreader';
42
- import { extend } from 'ol/extent';
43
47
  import 'ol/ol.css';
48
+ import { LineString, Point } from 'ol/geom';
49
+ import { getArea, getLength } from 'ol/sphere';
50
+ import { Select, Modify } from 'ol/interaction';
51
+ import { Style, Text, Stroke, Fill } from 'ol/style';
52
+ import * as i3$2 from '@angular/material/core';
53
+ import { MatOptionModule } from '@angular/material/core';
54
+ import WKT from 'ol/format/WKT';
55
+ import { buffer as buffer$1 } from 'ol/extent';
56
+ import Feature from 'ol/Feature';
57
+ import { never, always } from 'ol/events/condition';
58
+ import { buffer, featureCollection, difference } from '@turf/turf';
59
+ import GeoJSON from 'ol/format/GeoJSON';
60
+ import * as i6$1 from '@angular/material/tooltip';
61
+ import { MatTooltipModule } from '@angular/material/tooltip';
62
+ import * as i5$1 from '@angular/material/autocomplete';
63
+ import { MatAutocompleteModule } from '@angular/material/autocomplete';
44
64
 
45
65
  const GISKOMPONENT_CONFIG = new InjectionToken('GisKomponentConfig');
46
66
 
@@ -52,7 +72,15 @@ class ProfileService {
52
72
  const url = `${this._baseUrl}/api/profile/identifier/${identifier}`;
53
73
  return this._http.get(url);
54
74
  }
75
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.15", ngImport: i0, type: ProfileService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
76
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.15", ngImport: i0, type: ProfileService, providedIn: 'root' });
55
77
  }
78
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.15", ngImport: i0, type: ProfileService, decorators: [{
79
+ type: Injectable,
80
+ args: [{
81
+ providedIn: 'root'
82
+ }]
83
+ }] });
56
84
 
57
85
  // logo-control.ts
58
86
  class LogoControl extends Control {
@@ -84,9 +112,6 @@ class CopyrightControl extends Control {
84
112
  }
85
113
  }
86
114
 
87
- Injectable({
88
- providedIn: 'root'
89
- });
90
115
  class LayerHelperService {
91
116
  _layerDbIdKey = 'layerDbId';
92
117
  _layerGroupDbIdKey = 'layerGroupDbId';
@@ -106,9 +131,11 @@ class LayerHelperService {
106
131
  const dbId = layer.get(this._layerGroupDbIdKey) ?? -1;
107
132
  return +dbId;
108
133
  }
109
- applyCachedLayersToDisplayInMap(map) {
110
- const layerIdsCachedToDisplay = localStorage.getItem(this._layerIdsToDisplayInMapKeyName)?.split(',');
134
+ applyCachedLayersToDisplayInMap(map, profileId) {
135
+ const layerIdsCachedToDisplay = localStorage.getItem(`${this._layerIdsToDisplayInMapKeyName}_${profileId}`)?.split(',');
111
136
  if (layerIdsCachedToDisplay) {
137
+ //Bottom layers are rendered first, top layers are rendered last (on top)
138
+ layerIdsCachedToDisplay.reverse();
112
139
  map.getLayers().getArray().forEach(layergroup => {
113
140
  if (layergroup instanceof OLLayerGroup) {
114
141
  layergroup.getLayers().getArray().forEach(l => {
@@ -118,31 +145,179 @@ class LayerHelperService {
118
145
  l.setVisible(!current);
119
146
  }
120
147
  }
148
+ else {
149
+ //Set z index to match the order in cache
150
+ l.setZIndex(layerIdsCachedToDisplay.indexOf(l.get(this._layerDbIdKey).toString()));
151
+ //console.log('layer id '+l.get(this._layerDbIdKey)+' - setZIndex:'+layerIdsCachedToDisplay.indexOf(l.get(this._layerDbIdKey).toString()));
152
+ }
121
153
  });
122
154
  }
123
155
  });
124
156
  }
125
157
  }
126
- toggleLayer(map, layer, filteredLayerGroups) {
158
+ updateLayerOpacityInMap(map, layer) {
159
+ map.getLayers().getArray().forEach(layergroup => {
160
+ if (layergroup instanceof OLLayerGroup) {
161
+ layergroup.getLayers().getArray().forEach(l => {
162
+ if (l.get(this._layerDbIdKey) === layer.id && layer.opacity) {
163
+ l.setOpacity(layer.opacity);
164
+ }
165
+ });
166
+ }
167
+ });
168
+ }
169
+ toggleLayerInMap(map, layer) {
127
170
  map.getLayers().getArray().forEach(layergroup => {
128
171
  if (layergroup instanceof OLLayerGroup) {
129
172
  layergroup.getLayers().getArray().forEach(l => {
130
173
  if (l.get(this._layerDbIdKey) === layer.id) {
131
174
  const current = l.getVisible();
132
175
  l.setVisible(!current);
133
- layer.visible = !current;
134
- //console.log('set '+ layer.id +' to visible '+layer.displayInMap);
135
- localStorage.setItem(this._mapFilteredLayerGroupsKeyName, JSON.stringify(filteredLayerGroups));
136
- localStorage.setItem(this._layerIdsToDisplayInMapKeyName, filteredLayerGroups
137
- .flatMap(lg => lg.layers)
138
- .filter(lg => lg.visible)
139
- .map(lg => lg.id).join(','));
140
176
  }
141
177
  });
142
178
  }
143
179
  });
144
180
  }
181
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.15", ngImport: i0, type: LayerHelperService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
182
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.15", ngImport: i0, type: LayerHelperService, providedIn: 'root' });
183
+ }
184
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.15", ngImport: i0, type: LayerHelperService, decorators: [{
185
+ type: Injectable,
186
+ args: [{
187
+ providedIn: 'root'
188
+ }]
189
+ }] });
190
+
191
+ class LibNotificationComponent {
192
+ data = inject(MAT_SNACK_BAR_DATA);
193
+ _snackRef = inject((MatSnackBarRef));
194
+ dismiss() {
195
+ this._snackRef.dismiss();
196
+ }
197
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.15", ngImport: i0, type: LibNotificationComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
198
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.15", type: LibNotificationComponent, isStandalone: true, selector: "lib-notification", ngImport: i0, template: "<div class=\"notification-container\">\n <button mat-icon-button class=\"close-btn\" (click)=\"dismiss()\">&times;</button>\n @for (message of data.messages; track message) {\n <div class=\"lib-notification\">\n <span>{{ message }}</span>\n </div>\n }\n</div>", styles: ["@charset \"UTF-8\";:host{display:block}.notification-container{display:flex;flex-direction:column;gap:12px;padding:8px;min-width:300px;max-width:480px}.close-btn{position:absolute;top:12px;right:12px;width:32px;height:32px;background:#ffffffe6;border:none;border-radius:50%;color:#666;font-size:20px;cursor:pointer;display:flex;align-items:center;justify-content:center;transition:all .2s cubic-bezier(.4,0,.2,1);backdrop-filter:blur(8px);z-index:10}.close-btn:hover{background:#fff;color:#333}.lib-notification{position:relative;background:linear-gradient(135deg,#d32f2f,#b71c1c);color:#fff;padding:16px 52px 16px 20px;border-radius:12px;box-shadow:0 4px 20px #d32f2f4d,0 2px 8px #00000026,inset 0 1px #fff3;font-weight:500;font-size:14px;line-height:1.5;border:1px solid rgba(255,255,255,.1);overflow:hidden;backdrop-filter:blur(10px);animation-fill-mode:forwards}.lib-notification:before{content:\"\";position:absolute;top:0;left:0;right:0;height:3px;background:linear-gradient(90deg,#fffc,#fff6);border-radius:12px 12px 0 0;animation:progress 5s linear forwards}.lib-notification:hover{transform:translateY(-1px);box-shadow:0 6px 25px #d32f2f66,0 4px 12px #0000002e,inset 0 1px #fff3;transition:all .2s ease}.lib-notification.success{background:linear-gradient(135deg,#2e7d32,#1b5e20);box-shadow:0 4px 20px #2e7d324d,0 2px 8px #00000026}.lib-notification.success:before{background:linear-gradient(90deg,#fffc,#fff6)}.lib-notification.success:after{content:\"\\2705\"}.lib-notification.warning{background:linear-gradient(135deg,#f57c00,#e65100);box-shadow:0 4px 20px #f57c004d,0 2px 8px #00000026}.lib-notification.warning:before{background:linear-gradient(90deg,#fffc,#fff6)}.lib-notification.info{background:linear-gradient(135deg,#0288d1,#01579b);box-shadow:0 4px 20px #0288d14d,0 2px 8px #00000026}.lib-notification.info:before{background:linear-gradient(90deg,#fffc,#fff6)}.lib-notification.info:after{content:\"\\2139\\fe0f\"}@keyframes slideIn{0%{opacity:0;transform:translate(100%) scale(.9)}to{opacity:1;transform:translate(0) scale(1)}}@keyframes progress{0%{width:100%}to{width:0%}}@media (max-width: 480px){.notification-container{min-width:calc(100vw - 32px);max-width:calc(100vw - 32px);margin:0 16px}.lib-notification{padding:14px 48px 14px 16px;font-size:13px}.close-btn{top:8px;right:8px;width:28px;height:28px;font-size:18px}}\n"] });
199
+ }
200
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.15", ngImport: i0, type: LibNotificationComponent, decorators: [{
201
+ type: Component,
202
+ args: [{ selector: 'lib-notification', standalone: true, template: "<div class=\"notification-container\">\n <button mat-icon-button class=\"close-btn\" (click)=\"dismiss()\">&times;</button>\n @for (message of data.messages; track message) {\n <div class=\"lib-notification\">\n <span>{{ message }}</span>\n </div>\n }\n</div>", styles: ["@charset \"UTF-8\";:host{display:block}.notification-container{display:flex;flex-direction:column;gap:12px;padding:8px;min-width:300px;max-width:480px}.close-btn{position:absolute;top:12px;right:12px;width:32px;height:32px;background:#ffffffe6;border:none;border-radius:50%;color:#666;font-size:20px;cursor:pointer;display:flex;align-items:center;justify-content:center;transition:all .2s cubic-bezier(.4,0,.2,1);backdrop-filter:blur(8px);z-index:10}.close-btn:hover{background:#fff;color:#333}.lib-notification{position:relative;background:linear-gradient(135deg,#d32f2f,#b71c1c);color:#fff;padding:16px 52px 16px 20px;border-radius:12px;box-shadow:0 4px 20px #d32f2f4d,0 2px 8px #00000026,inset 0 1px #fff3;font-weight:500;font-size:14px;line-height:1.5;border:1px solid rgba(255,255,255,.1);overflow:hidden;backdrop-filter:blur(10px);animation-fill-mode:forwards}.lib-notification:before{content:\"\";position:absolute;top:0;left:0;right:0;height:3px;background:linear-gradient(90deg,#fffc,#fff6);border-radius:12px 12px 0 0;animation:progress 5s linear forwards}.lib-notification:hover{transform:translateY(-1px);box-shadow:0 6px 25px #d32f2f66,0 4px 12px #0000002e,inset 0 1px #fff3;transition:all .2s ease}.lib-notification.success{background:linear-gradient(135deg,#2e7d32,#1b5e20);box-shadow:0 4px 20px #2e7d324d,0 2px 8px #00000026}.lib-notification.success:before{background:linear-gradient(90deg,#fffc,#fff6)}.lib-notification.success:after{content:\"\\2705\"}.lib-notification.warning{background:linear-gradient(135deg,#f57c00,#e65100);box-shadow:0 4px 20px #f57c004d,0 2px 8px #00000026}.lib-notification.warning:before{background:linear-gradient(90deg,#fffc,#fff6)}.lib-notification.info{background:linear-gradient(135deg,#0288d1,#01579b);box-shadow:0 4px 20px #0288d14d,0 2px 8px #00000026}.lib-notification.info:before{background:linear-gradient(90deg,#fffc,#fff6)}.lib-notification.info:after{content:\"\\2139\\fe0f\"}@keyframes slideIn{0%{opacity:0;transform:translate(100%) scale(.9)}to{opacity:1;transform:translate(0) scale(1)}}@keyframes progress{0%{width:100%}to{width:0%}}@media (max-width: 480px){.notification-container{min-width:calc(100vw - 32px);max-width:calc(100vw - 32px);margin:0 16px}.lib-notification{padding:14px 48px 14px 16px;font-size:13px}.close-btn{top:8px;right:8px;width:28px;height:28px;font-size:18px}}\n"] }]
203
+ }] });
204
+
205
+ class LibErrorService {
206
+ _snackBar = inject(MatSnackBar);
207
+ _messages = [];
208
+ //Format errors from backend or UI
209
+ format(error) {
210
+ if (error instanceof HttpErrorResponse) {
211
+ if (error.error?.message)
212
+ return error.error.message;
213
+ if (typeof error.error === 'string')
214
+ return error.error;
215
+ return `Server returned ${error.status}`;
216
+ }
217
+ if (error instanceof Error)
218
+ return error.message;
219
+ return 'An unexpected error occurred.';
220
+ }
221
+ // Log errors (for debugging or telemetry)
222
+ log(error) {
223
+ console.error('[Library Error]', error);
224
+ }
225
+ // Show user-friendly message via Material snackbar
226
+ show(error) {
227
+ const message = this.format(error);
228
+ // Prevent duplicates
229
+ if (this._messages.includes(message))
230
+ return;
231
+ this._messages.push(message);
232
+ const ref = this._snackBar.openFromComponent(LibNotificationComponent, {
233
+ data: { messages: this._messages },
234
+ duration: 5000,
235
+ horizontalPosition: 'right',
236
+ verticalPosition: 'top',
237
+ politeness: 'off',
238
+ panelClass: ['lib-notification-container'],
239
+ });
240
+ // Remove from active stack when it closes
241
+ ref.afterDismissed().subscribe(() => {
242
+ this._messages = this._messages.filter(m => m !== message);
243
+ });
244
+ }
245
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.15", ngImport: i0, type: LibErrorService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
246
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.15", ngImport: i0, type: LibErrorService, providedIn: 'root' });
145
247
  }
248
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.15", ngImport: i0, type: LibErrorService, decorators: [{
249
+ type: Injectable,
250
+ args: [{ providedIn: 'root' }]
251
+ }] });
252
+
253
+ class LayerErrorService {
254
+ layerStatusChanged = new Subject();
255
+ layerStatuses = new Map();
256
+ listeners = new Map();
257
+ _errorService = inject(LibErrorService);
258
+ registerLayer(layerId, layer) {
259
+ // BaseLayer does not guarantee getSource(), so we must narrow type first
260
+ let source;
261
+ if (layer instanceof TileLayer || layer instanceof ImageLayer || layer instanceof VectorLayer) {
262
+ source = layer.getSource();
263
+ }
264
+ if (!source) {
265
+ console.warn(`[Layer ${layerId}] has no source; skipping error tracking.`);
266
+ return;
267
+ }
268
+ const markError = (event) => {
269
+ this.layerStatuses.set(layerId, { layerId, hasError: true, lastError: event });
270
+ console.error(`[Layer ${layerId}] load error:`, event);
271
+ this._errorService.show(new Error(`Indlæsningsfejl for lag med id ${layerId}.`));
272
+ this.layerStatusChanged.next(layerId);
273
+ };
274
+ const clearError = () => {
275
+ this.layerStatuses.set(layerId, { layerId, hasError: false });
276
+ this.layerStatusChanged.next(layerId);
277
+ };
278
+ let keys = [];
279
+ if (source instanceof TileSource) {
280
+ keys.push(source.on('tileloaderror', markError));
281
+ keys.push(source.on('tileloadend', clearError));
282
+ }
283
+ else if (source instanceof ImageSource) {
284
+ keys.push(source.on('imageloaderror', markError));
285
+ keys.push(source.on('imageloadend', clearError));
286
+ }
287
+ else if (source instanceof VectorSource) {
288
+ keys.push(source.on('featuresloaderror', markError));
289
+ keys.push(source.on('featuresloadend', clearError));
290
+ }
291
+ else {
292
+ console.warn(`[Layer ${layerId}] Unknown source type; cannot attach error tracking.`);
293
+ }
294
+ if (keys.length) {
295
+ this.listeners.set(layerId, keys);
296
+ }
297
+ this.layerStatusChanged.next(layerId);
298
+ }
299
+ unregisterLayer(layerId) {
300
+ const keys = this.listeners.get(layerId);
301
+ if (keys) {
302
+ keys.forEach(k => unByKey(k));
303
+ this.listeners.delete(layerId);
304
+ }
305
+ this.layerStatuses.delete(layerId);
306
+ this.layerStatusChanged.next(layerId);
307
+ }
308
+ getLayerStatus(layerId) {
309
+ return this.layerStatuses.get(layerId);
310
+ }
311
+ getAllStatuses() {
312
+ return Array.from(this.layerStatuses.values());
313
+ }
314
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.15", ngImport: i0, type: LayerErrorService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
315
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.15", ngImport: i0, type: LayerErrorService, providedIn: 'root' });
316
+ }
317
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.15", ngImport: i0, type: LayerErrorService, decorators: [{
318
+ type: Injectable,
319
+ args: [{ providedIn: 'root' }]
320
+ }] });
146
321
 
147
322
  class LayerSelectorComponent {
148
323
  set contentIcon(content) {
@@ -153,10 +328,12 @@ class LayerSelectorComponent {
153
328
  }
154
329
  map;
155
330
  profile;
331
+ currentZoomLevel;
156
332
  searchText = '';
157
333
  allLayerGroups = [];
158
334
  filteredLayerGroups = [];
159
335
  showSelector = false;
336
+ _namesToGoLast = ['Baggrundskort'];
160
337
  _layerSelectorIcon;
161
338
  _layerSelectorIconControl;
162
339
  _layerSelectorBody;
@@ -164,38 +341,92 @@ class LayerSelectorComponent {
164
341
  _mapFilteredLayerGroupsKeyName = 'mapFilteredLayerGroups';
165
342
  _layerIdsToDisplayInMapKeyName = 'layerIdsToDisplayInMap';
166
343
  _layerHelper = inject(LayerHelperService);
167
- _bypassLayersCache = true; // TO DO - remove this when we fully implement the caching
168
- ngOnChanges() {
169
- if (this.profile) {
344
+ _layerErrorService = inject(LayerErrorService);
345
+ ngOnInit() {
346
+ this._layerErrorService.layerStatusChanged
347
+ .subscribe(layerId => {
348
+ const layerHasErrors = this._layerErrorService.getLayerStatus(layerId)?.hasError;
349
+ if (layerHasErrors != undefined)
350
+ this.setLayerErrorStatus(layerId, layerHasErrors);
351
+ });
352
+ }
353
+ ngOnChanges(changes) {
354
+ if (changes['profile'] && this.profile) {
170
355
  this.allLayerGroups = this.profile.layerGroups
171
- .map((lg, idx) => ({ ...lg, layers: lg.layers
172
- .filter((l) => l.activeInSelector),
356
+ .map((lg, idx) => ({ ...lg,
357
+ noOfVisibleLayers: lg.layers.filter(l => l.visible).length,
358
+ visible: lg.layers.some(l => l.visible),
359
+ expanded: lg.layers.some(l => l.visible),
360
+ layers: lg.layers.filter((l) => l.activeInSelector),
173
361
  sortOrder: idx }))
174
362
  .filter(g => g.layers.length > 0);
175
- const cachedFilteredLayergroups = localStorage.getItem(this._mapFilteredLayerGroupsKeyName);
176
- if (!this._bypassLayersCache && cachedFilteredLayergroups) {
177
- const parsedFilteredLayerGroups = JSON.parse(cachedFilteredLayergroups);
178
- if (parsedFilteredLayerGroups.length != this.allLayerGroups.length) {
179
- //If layer groups changed in the backend, then use those
363
+ const cacheValue = localStorage.getItem(`${this._mapFilteredLayerGroupsKeyName}_${this.profile.id}`);
364
+ if (cacheValue) {
365
+ const cachedProfileInfo = JSON.parse(cacheValue);
366
+ if (cachedProfileInfo.profile.versionId < this.profile.versionId) {
367
+ //Profile version changed in backend, use that
180
368
  this.filteredLayerGroups = this.setfilteredGroups();
369
+ this._cacheProfileInfo();
181
370
  }
182
371
  else {
183
- this.filteredLayerGroups = parsedFilteredLayerGroups;
184
- this._setVisibleLayersFromCache(parsedFilteredLayerGroups);
372
+ this.filteredLayerGroups = cachedProfileInfo.cachedLayerGroups;
373
+ this._setVisibleLayersFromCache(cachedProfileInfo.cachedLayerGroups);
185
374
  }
186
375
  }
187
376
  else {
188
377
  this.filteredLayerGroups = this.setfilteredGroups();
378
+ this._cacheProfileInfo();
189
379
  }
190
- localStorage.setItem(this._layerIdsToDisplayInMapKeyName, this.filteredLayerGroups
191
- .flatMap(lg => lg.layers)
192
- .filter(lg => lg.visible)
193
- .map(lg => lg.id).join(','));
194
380
  this._initializeMapIconControl();
195
381
  }
196
382
  }
197
383
  toggleLayer(layer) {
198
- this._layerHelper.toggleLayer(this.map, layer, this.filteredLayerGroups);
384
+ this._layerHelper.toggleLayerInMap(this.map, layer);
385
+ // Toggle layer in all groups in the selector UI
386
+ this.filteredLayerGroups.forEach(group => {
387
+ group.layers.forEach(l => {
388
+ if (layer.id === l.id) {
389
+ l.visible = !l.visible;
390
+ }
391
+ });
392
+ group.noOfVisibleLayers = group.layers.filter(l => l.visible).length;
393
+ group.visible = group.layers.some(l => l.visible);
394
+ group.expanded = group.layers.some(l => l.visible);
395
+ });
396
+ this._cacheProfileInfo();
397
+ }
398
+ setLayerErrorStatus(layerId, errorStatus) {
399
+ this.filteredLayerGroups.forEach(group => {
400
+ group.layers.forEach(l => {
401
+ if (layerId === l.id) {
402
+ l.hasErrors = errorStatus;
403
+ }
404
+ });
405
+ });
406
+ }
407
+ toggleGroup(event, layerGroup) {
408
+ event.stopPropagation(); // Prevent the panel from expanding/collapsing
409
+ const visible = !layerGroup.visible;
410
+ layerGroup.layers.forEach(layer => {
411
+ if (layer.visible != visible) {
412
+ //Toggle layer in map
413
+ this._layerHelper.toggleLayerInMap(this.map, layer);
414
+ // Toggle layer in all groups in the selector UI
415
+ this.filteredLayerGroups.forEach(group => {
416
+ group.layers.forEach(l => {
417
+ if (layer.id === l.id) {
418
+ l.visible = !l.visible;
419
+ }
420
+ });
421
+ group.noOfVisibleLayers = group.layers.filter(l => l.visible).length;
422
+ group.visible = group.layers.some(l => l.visible);
423
+ group.expanded = group.layers.some(l => l.visible);
424
+ });
425
+ }
426
+ });
427
+ layerGroup.visible = visible;
428
+ layerGroup.expanded = layerGroup.layers.some(l => l.visible);
429
+ this._cacheProfileInfo();
199
430
  }
200
431
  toggleLayerSelector() {
201
432
  this.showSelector = !this.showSelector;
@@ -206,28 +437,44 @@ class LayerSelectorComponent {
206
437
  this._removeMapBodyControl();
207
438
  }
208
439
  }
440
+ updateOpacity(layer) {
441
+ this._layerHelper.updateLayerOpacityInMap(this.map, layer);
442
+ }
443
+ stopDrag(event) {
444
+ event.stopPropagation(); // prevents drag container from activating
445
+ }
209
446
  setfilteredGroups() {
210
447
  const text = this.searchText.toLowerCase();
211
448
  return this.allLayerGroups
212
449
  .map((g, idx) => {
213
450
  if (!text) {
214
- return { ...g, expanded: false, sortOrder: idx }; // collapsed by default
451
+ return { ...g, expanded: false, sortOrder: idx,
452
+ layers: g.layers.map(layer => ({ ...layer, opacity: 1 }))
453
+ }; // collapsed by default
215
454
  }
216
455
  const filteredItems = g.layers.filter(l => l.name.toLowerCase().includes(text));
217
456
  return {
218
457
  ...g,
219
458
  expanded: filteredItems.length > 0,
220
459
  sortOrder: idx,
221
- layers: filteredItems
460
+ layers: filteredItems.map(layer => ({ ...layer, opacity: 1 }))
222
461
  };
223
462
  })
224
463
  .filter(g => g.layers.length > 0)
225
- .sort((a, b) => a.sortOrder - b.sortOrder);
464
+ .sort((a, b) => {
465
+ const aIsLast = this._namesToGoLast.includes(a.name) || a.layers.some(layer => layer.background);
466
+ const bIsLast = this._namesToGoLast.includes(b.name) || b.layers.some(layer => layer.background);
467
+ if (aIsLast && !bIsLast)
468
+ return 1;
469
+ if (!aIsLast && bIsLast)
470
+ return -1;
471
+ return a.sortOrder - b.sortOrder;
472
+ });
226
473
  }
227
474
  dropGroup(event) {
228
475
  moveItemInArray(this.filteredLayerGroups, event.previousIndex, event.currentIndex);
229
476
  this.updateGroupSortOrders();
230
- localStorage.setItem(this._mapFilteredLayerGroupsKeyName, JSON.stringify(this.filteredLayerGroups));
477
+ this._cacheProfileInfo();
231
478
  }
232
479
  dropLayer(event, group) {
233
480
  if (event.previousContainer === event.container) {
@@ -237,11 +484,12 @@ class LayerSelectorComponent {
237
484
  transferArrayItem(event.previousContainer.data, event.container.data, event.previousIndex, event.currentIndex);
238
485
  }
239
486
  this.updateLayerSortOrders(group);
240
- localStorage.setItem(this._mapFilteredLayerGroupsKeyName, JSON.stringify(this.filteredLayerGroups));
487
+ this._cacheProfileInfo();
241
488
  }
242
489
  clearSearchText() {
243
490
  this.searchText = '';
244
491
  this.filteredLayerGroups = this.setfilteredGroups();
492
+ this._cacheProfileInfo();
245
493
  }
246
494
  updateGroupSortOrders() {
247
495
  this.filteredLayerGroups.forEach((g, idx) => (g.sortOrder = idx));
@@ -249,6 +497,16 @@ class LayerSelectorComponent {
249
497
  updateLayerSortOrders(group) {
250
498
  group.layers.forEach((layer, idx) => (layer.sortOrder = idx));
251
499
  }
500
+ _cacheProfileInfo() {
501
+ const cacheItem = { profile: this.profile, cachedLayerGroups: this.filteredLayerGroups };
502
+ localStorage.setItem(`${this._mapFilteredLayerGroupsKeyName}_${this.profile.id}`, JSON.stringify(cacheItem));
503
+ localStorage.setItem(`${this._layerIdsToDisplayInMapKeyName}_${this.profile.id}`, this.filteredLayerGroups
504
+ .flatMap(lg => lg.layers)
505
+ .filter(lg => lg.visible)
506
+ .map(lg => lg.id).join(','));
507
+ // Reflect new order in map
508
+ this._layerHelper.applyCachedLayersToDisplayInMap(this.map, this.profile.id);
509
+ }
252
510
  _initializeMapIconControl() {
253
511
  this.map.removeControl(this._layerSelectorIconControl);
254
512
  const element = this._layerSelectorIcon.nativeElement;
@@ -271,12 +529,12 @@ class LayerSelectorComponent {
271
529
  }));
272
530
  }
273
531
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.15", ngImport: i0, type: LayerSelectorComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
274
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.15", type: LayerSelectorComponent, isStandalone: true, selector: "lib-layer-selector", inputs: { map: "map", profile: "profile" }, viewQueries: [{ propertyName: "contentIcon", first: true, predicate: ["layerSelectorIcon"], descendants: true }, { propertyName: "contentBody", first: true, predicate: ["layerSelectorBody"], descendants: true }], usesOnChanges: true, ngImport: i0, template: "<div #layerSelectorIcon class=\"ol-unselectable ol-control layer-selector-icon\">\n <mat-icon (click)=\"toggleLayerSelector()\">layers</mat-icon>\n</div>\n<div #layerSelectorBody [class.display-none]=\"!showSelector\" class=\"ol-unselectable ol-control layer-selector-body\">\n <div class=\"search-section\">\n <mat-form-field appearance=\"outline\" class=\"w-full\">\n <mat-label>Filtrer</mat-label>\n <input \n matInput \n type=\"text\" \n [(ngModel)]=\"searchText\" \n (ngModelChange)=\"filteredLayerGroups=setfilteredGroups()\"\n placeholder=\"Skriv for at filtrere...\"\n />\n </mat-form-field>\n <mat-icon (click)=\"clearSearchText()\">undo</mat-icon>\n </div>\n \n <div\n cdkDropList\n [cdkDropListData]=\"filteredLayerGroups\"\n (cdkDropListDropped)=\"dropGroup($event)\"\n class=\"item-list\">\n @for (group of filteredLayerGroups; track group.id; let gIndex = $index) {\n <div class=\"group\" cdkDrag cdkDragPreviewDisabled>\n <mat-expansion-panel [(expanded)]=\"group.expanded\">\n <mat-expansion-panel-header>\n <mat-panel-title>\n @if (group.expanded) {\n <mat-icon>arrow_upward</mat-icon>\n }\n @if (!group.expanded) {\n <mat-icon>arrow_downward</mat-icon> \n }\n {{ group.name }}\n </mat-panel-title>\n </mat-expansion-panel-header>\n <!-- This is only shown during drag -->\n <!-- <div *cdkDragPreview class=\"drag-preview\">\n {{ group.name }}\n </div> -->\n\n <!-- Placeholder to avoid jump -->\n <!-- <div *cdkDragPlaceholder class=\"drag-placeholder\"></div> -->\n\n <div\n cdkDropList\n [cdkDropListData]=\"group.layers\"\n (cdkDropListDropped)=\"dropLayer($event, group)\"\n class=\"item-list\">\n @for (layer of group.layers; track layer.id; let iIndex = $index) {\n <div class=\"item\" cdkDrag cdkDragPreviewDisabled>\n <mat-icon class=\"drag-indicator\">drag_indicator</mat-icon>\n <span>{{ layer.name }}</span>\n @if (layer.visible) {\n <mat-icon (click)=\"toggleLayer(layer)\" class=\"power-on\">power</mat-icon>(t\u00E6nd)\n }\n @if (!layer.visible) {\n <mat-icon (click)=\"toggleLayer(layer)\" class=\"power-off\">power</mat-icon>(sluk)\n }\n </div>\n }\n </div>\n </mat-expansion-panel>\n </div>\n }\n </div>\n</div>\n", styles: ["::ng-deep .layer-selector-icon{position:absolute;left:auto;right:4em;bottom:.5em;z-index:1000}::ng-deep .layer-selector-icon mat-icon{display:flex;justify-content:center;align-items:center;cursor:pointer;transition:all .3s ease;font-size:45px;width:50px;height:50px;background-color:#0004;border:1px solid rgba(255,255,255,.8);border-radius:8px;-webkit-text-fill-color:#334155;-webkit-text-stroke-width:.9px;-webkit-text-stroke-color:white}::ng-deep .layer-selector-icon mat-icon:hover{box-shadow:0 4px 12px #0003;color:#3e4b5e}::ng-deep .layer-selector-body{position:absolute;left:auto;right:4em;bottom:4em;z-index:1000;background:#fff;border-radius:8px;box-shadow:0 4px 20px #00000026;border:1px solid #e0e0e0;width:320px;max-height:500px;overflow:hidden;display:flex;flex-direction:column}::ng-deep .layer-selector-body .search-section{display:flex;align-items:center;gap:8px;padding:16px 16px 8px;background:#fff;border-bottom:1px solid #e0e0e0}::ng-deep .layer-selector-body .search-section mat-form-field{flex:1}::ng-deep .layer-selector-body .search-section mat-form-field .mat-form-field-outline{background:#fff}::ng-deep .layer-selector-body .search-section mat-form-field .mat-form-field-wrapper{padding-bottom:0}::ng-deep .layer-selector-body .search-section mat-form-field .mat-form-field-outline-thick{color:#1976d2}::ng-deep .layer-selector-body .search-section mat-form-field input{font-size:14px;padding:8px 0}::ng-deep .layer-selector-body .search-section mat-form-field .mat-form-field-label{color:#666;font-weight:500}::ng-deep .layer-selector-body .search-section mat-icon{color:#666;cursor:pointer;padding:8px;border-radius:4px;transition:all .2s ease;font-size:30px;width:20px;height:20px;display:flex;justify-content:center;align-items:end}::ng-deep .layer-selector-body .search-section mat-icon:hover{color:#334155}::ng-deep .layer-selector-body .item-list{flex:1;overflow-y:auto;padding:8px;max-height:400px}::ng-deep .layer-selector-body .item-list .group{margin-bottom:8px;border-radius:6px;overflow:hidden;box-shadow:0 -2px 2px #4868b20a,0 2px 2px #6a6f7517,0 1px 2px #4868b214}::ng-deep .layer-selector-body .item-list .group mat-expansion-panel{box-shadow:none!important;border-radius:0!important}::ng-deep .layer-selector-body .item-list .group mat-expansion-panel mat-expansion-panel-header{padding:0 16px;height:48px;background:#f8f9fa;border-bottom:1px solid #e0e0e0}::ng-deep .layer-selector-body .item-list .group mat-expansion-panel mat-expansion-panel-header:hover{background:#e6e7ec}::ng-deep .layer-selector-body .item-list .group mat-expansion-panel mat-expansion-panel-header mat-panel-title{align-items:center;gap:8px;font-weight:600;color:#333;font-size:14px}::ng-deep .layer-selector-body .item-list .group mat-expansion-panel mat-expansion-panel-header mat-panel-title mat-icon{color:#666;font-size:18px;width:18px;height:18px;transition:transform .2s ease}::ng-deep .layer-selector-body .item-list .group mat-expansion-panel .mat-expansion-panel-content{background:#fff}::ng-deep .layer-selector-body .item-list .group mat-expansion-panel .mat-expansion-panel-content .mat-expansion-panel-body{padding:8px 0}::ng-deep .layer-selector-body .item-list .group .item-list{padding:0;max-height:none}::ng-deep .layer-selector-body .item-list .group .item-list .item{display:flex;align-items:center;gap:8px;padding:12px 16px;border-bottom:1px solid #f5f5f5;background:#fff;transition:all .2s ease;cursor:move}::ng-deep .layer-selector-body .item-list .group .item-list .item:last-child{border-bottom:none}::ng-deep .layer-selector-body .item-list .group .item-list .item:hover{background:#f8f9fa}::ng-deep .layer-selector-body .item-list .group .item-list .item.cdk-drag-preview{background:#fff;box-shadow:0 4px 12px #00000026;border-radius:4px}::ng-deep .layer-selector-body .item-list .group .item-list .item.cdk-drag-placeholder{background:#e3f2fd;opacity:.6}::ng-deep .layer-selector-body .item-list .group .item-list .item.cdk-drag-animating{transition:transform .25s cubic-bezier(0,0,.2,1)}::ng-deep .layer-selector-body .item-list .group .item-list .item mat-icon.drag-indicator{color:#9e9e9e;font-size:18px;width:18px;height:18px;cursor:move}::ng-deep .layer-selector-body .item-list .group .item-list .item mat-icon.power-on{color:#4caf50;font-size:20px;width:20px;height:20px;cursor:pointer;margin-left:auto;padding:4px;border-radius:4px}::ng-deep .layer-selector-body .item-list .group .item-list .item mat-icon.power-on:hover{background:#e8f5e8}::ng-deep .layer-selector-body .item-list .group .item-list .item mat-icon.power-off{color:#f44336;font-size:20px;width:20px;height:20px;cursor:pointer;margin-left:auto;padding:4px;border-radius:4px}::ng-deep .layer-selector-body .item-list .group .item-list .item mat-icon.power-off:hover{background:#ffebee}::ng-deep .layer-selector-body .item-list .group .item-list .item>:nth-child(2){flex:1;font-size:14px;color:#333;margin:0}.cdk-drag-preview{box-sizing:border-box;border-radius:4px;box-shadow:0 5px 15px #00000026;background:#fff;padding:12px 16px}.cdk-drag-placeholder{opacity:.3}.cdk-drag-animating{transition:transform .25s cubic-bezier(0,0,.2,1)}.item-list.cdk-drop-list-dragging .item:not(.cdk-drag-placeholder){transition:transform .25s cubic-bezier(0,0,.2,1)}.power-on{color:#4caf50}.power-off{color:#f44336}.display-none{display:none}::ng-deep .layer-selector-body .item-list{scrollbar-width:thin;scrollbar-color:#c1c1c1 transparent}\n"], dependencies: [{ kind: "ngmodule", type: MatFormFieldModule }, { kind: "component", type: i1.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i1.MatLabel, selector: "mat-label" }, { kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i2.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i3.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i3.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i3.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: DragDropModule }, { kind: "directive", type: i4.CdkDropList, selector: "[cdkDropList], cdk-drop-list", inputs: ["cdkDropListConnectedTo", "cdkDropListData", "cdkDropListOrientation", "id", "cdkDropListLockAxis", "cdkDropListDisabled", "cdkDropListSortingDisabled", "cdkDropListEnterPredicate", "cdkDropListSortPredicate", "cdkDropListAutoScrollDisabled", "cdkDropListAutoScrollStep", "cdkDropListElementContainer"], outputs: ["cdkDropListDropped", "cdkDropListEntered", "cdkDropListExited", "cdkDropListSorted"], exportAs: ["cdkDropList"] }, { kind: "directive", type: i4.CdkDrag, selector: "[cdkDrag]", inputs: ["cdkDragData", "cdkDragLockAxis", "cdkDragRootElement", "cdkDragBoundary", "cdkDragStartDelay", "cdkDragFreeDragPosition", "cdkDragDisabled", "cdkDragConstrainPosition", "cdkDragPreviewClass", "cdkDragPreviewContainer", "cdkDragScale"], outputs: ["cdkDragStarted", "cdkDragReleased", "cdkDragEnded", "cdkDragEntered", "cdkDragExited", "cdkDragDropped", "cdkDragMoved"], exportAs: ["cdkDrag"] }, { kind: "ngmodule", type: MatExpansionModule }, { kind: "component", type: i5.MatExpansionPanel, selector: "mat-expansion-panel", inputs: ["hideToggle", "togglePosition"], outputs: ["afterExpand", "afterCollapse"], exportAs: ["matExpansionPanel"] }, { kind: "component", type: i5.MatExpansionPanelHeader, selector: "mat-expansion-panel-header", inputs: ["expandedHeight", "collapsedHeight", "tabIndex"] }, { kind: "directive", type: i5.MatExpansionPanelTitle, selector: "mat-panel-title" }, { kind: "ngmodule", type: MatInputModule }, { kind: "directive", type: i6.MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly", "disabledInteractive"], exportAs: ["matInput"] }] });
532
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.15", type: LayerSelectorComponent, isStandalone: true, selector: "lib-layer-selector", inputs: { map: "map", profile: "profile", currentZoomLevel: "currentZoomLevel" }, viewQueries: [{ propertyName: "contentIcon", first: true, predicate: ["layerSelectorIcon"], descendants: true }, { propertyName: "contentBody", first: true, predicate: ["layerSelectorBody"], descendants: true }], usesOnChanges: true, ngImport: i0, template: "<div #layerSelectorIcon class=\"ol-unselectable ol-control layer-selector-icon\">\n <mat-icon (click)=\"toggleLayerSelector()\">layers</mat-icon>\n</div>\n<div #layerSelectorBody [class.display-none]=\"!showSelector\" class=\"layer-selector-body-wrapper\" cdkDrag cdkDragBoundary=\".map-container\">\n <div class=\"drag-handle-selector\" cdkDragHandle>\n <mat-icon>drag_indicator</mat-icon>\n </div>\n <div class=\"ol-unselectable ol-control layer-selector-body\">\n <div class=\"search-section\">\n <mat-form-field appearance=\"outline\" class=\"w-full\">\n <mat-label>Filtrer</mat-label>\n <input \n matInput \n type=\"text\" \n [(ngModel)]=\"searchText\" \n (ngModelChange)=\"filteredLayerGroups=setfilteredGroups()\"\n placeholder=\"Skriv for at filtrere...\"\n />\n </mat-form-field>\n <mat-icon (click)=\"clearSearchText()\">undo</mat-icon>\n </div>\n \n <div\n cdkDropList\n [cdkDropListData]=\"filteredLayerGroups\"\n (cdkDropListDropped)=\"dropGroup($event)\"\n class=\"item-list\">\n @for (group of filteredLayerGroups; track group.id; let gIndex = $index) {\n <div class=\"group\" cdkDrag cdkDragPreviewDisabled>\n <mat-expansion-panel [(expanded)]=\"group.expanded\">\n <mat-expansion-panel-header>\n <mat-panel-title>\n @if (group.expanded) {\n <mat-icon>arrow_upward</mat-icon>\n }\n @if (!group.expanded) {\n <mat-icon>arrow_downward</mat-icon> \n }\n {{ group.name }} \n <mat-icon class=\"lightbulb\">lightbulb</mat-icon>\n ({{ group.noOfVisibleLayers }}/{{ group.layers.length }})\n @if (group.visible) {\n <mat-icon (click)=\"toggleGroup($event, group)\" class=\"power-on\">power</mat-icon>(t\u00E6nd)\n }\n @if (!group.visible) {\n <mat-icon (click)=\"toggleGroup($event, group)\" class=\"power-off\">power_off</mat-icon>(sluk)\n }\n </mat-panel-title>\n </mat-expansion-panel-header>\n <!-- This is only shown during drag -->\n <!-- <div *cdkDragPreview class=\"drag-preview\">\n {{ group.name }}\n </div> -->\n\n <!-- Placeholder to avoid jump -->\n <!-- <div *cdkDragPlaceholder class=\"drag-placeholder\"></div> -->\n\n <div\n cdkDropList\n [cdkDropListData]=\"group.layers\"\n (cdkDropListDropped)=\"dropLayer($event, group)\"\n class=\"item-list\">\n @for (layer of group.layers; track layer.id; let iIndex = $index) {\n <div class=\"item\" cdkDrag cdkDragPreviewDisabled>\n <mat-icon class=\"drag-indicator\">drag_indicator</mat-icon>\n <span>{{ layer.name }}</span>\n @if (layer.maxZoom < currentZoomLevel || layer.minZoom > currentZoomLevel) {\n <mat-icon class=\"zoom-off\">browser_not_supported</mat-icon>(zoom)\n }\n @if (layer.hasErrors) {\n <mat-icon class=\"zoom-off\">wifi_off</mat-icon>(fejle)\n }\n @if (layer.visible) {\n <mat-icon (click)=\"toggleLayer(layer)\" class=\"power-on\">power</mat-icon>(t\u00E6nd)\n }\n @if (!layer.visible) {\n <mat-icon (click)=\"toggleLayer(layer)\" class=\"power-off\">power_off</mat-icon>(sluk)\n }\n <input \n type=\"range\"\n min=\"0\"\n max=\"1\"\n step=\"0.05\"\n [(ngModel)]=\"layer.opacity\"\n (input)=\"updateOpacity(layer)\"\n (mousedown)=\"stopDrag($event)\"\n (touchstart)=\"stopDrag($event)\"\n (pointerdown)=\"stopDrag($event)\"\n >\n </div>\n }\n </div>\n </mat-expansion-panel>\n </div>\n }\n </div>\n </div>\n</div>", styles: ["::ng-deep .layer-selector-icon{position:absolute;left:auto;right:4em;bottom:.5em;z-index:1000}::ng-deep .layer-selector-icon mat-icon{display:flex;justify-content:center;align-items:center;cursor:pointer;transition:all .3s ease;outline:none;border:0;height:40px;border-radius:5px;width:40px;background:#fff;box-shadow:#0000004d 0 1px 4px -1px;font-size:2em;color:#666}::ng-deep .layer-selector-icon mat-icon:hover{box-shadow:0 4px 12px #0003;color:#3e4b5e}.layer-selector-body-wrapper{position:absolute;left:auto;right:4em;bottom:4em;z-index:1000;cursor:grab}.layer-selector-body-wrapper.cdk-drag-dragging{opacity:.8;cursor:grab;z-index:1001}.layer-selector-body-wrapper .ol-control{border-radius:0}.drag-handle-selector{display:flex;align-items:center;justify-content:center;background:color-mix(in srgb,#000 40%,transparent);border-radius:4px 4px 0 0;padding:4px;cursor:grab;box-shadow:0 -2px 8px #0003}.drag-handle-selector mat-icon{color:#fff;font-size:20px;width:20px;height:20px}::ng-deep .layer-selector-body{position:relative;left:auto;right:auto;bottom:auto;z-index:auto;background:color-mix(in srgb,#000 40%,transparent);box-shadow:0 4px 20px #00000026;width:480px;max-height:500px;overflow:hidden;display:flex;flex-direction:column}::ng-deep .layer-selector-body .search-section{display:flex;align-items:center;gap:6px;padding:8px 12px 6px}::ng-deep .layer-selector-body .search-section mat-form-field{flex:1}::ng-deep .layer-selector-body .search-section mat-form-field .mat-mdc-text-field-wrapper{padding-bottom:0}::ng-deep .layer-selector-body .search-section mat-form-field .mat-form-field-wrapper{padding-bottom:0;margin-bottom:0}::ng-deep .layer-selector-body .search-section mat-form-field .mat-mdc-form-field-infix{padding-bottom:6px;min-height:auto}::ng-deep .layer-selector-body .search-section mat-form-field .mat-form-field-outline{background:#fff}::ng-deep .layer-selector-body .search-section mat-form-field .mat-form-field-outline-thick{color:#1976d2}::ng-deep .layer-selector-body .search-section mat-form-field input{font-size:13px;padding:3px 0}::ng-deep .layer-selector-body .search-section mat-form-field .mat-form-field-label{color:#fff;font-weight:500;font-size:13px}::ng-deep .layer-selector-body .search-section mat-form-field .mat-mdc-form-field-subscript-wrapper{height:0;margin-top:0}::ng-deep .layer-selector-body .search-section mat-icon{color:#fff;cursor:pointer;padding:6px;border-radius:4px;transition:all .2s ease;font-size:24px;width:24px;height:24px;display:flex;justify-content:center;align-items:center}::ng-deep .layer-selector-body .search-section mat-icon:hover{color:#f9fafb}::ng-deep .layer-selector-body .item-list{flex:1;overflow-y:auto;padding:6px;max-height:400px}::ng-deep .layer-selector-body .item-list .group{margin-bottom:6px;overflow:hidden;box-shadow:0 -2px 2px #4868b20a,0 2px 2px #6a6f7517,0 1px 2px #4868b214}::ng-deep .layer-selector-body .item-list .group mat-expansion-panel{box-shadow:none!important;border-radius:0!important}::ng-deep .layer-selector-body .item-list .group mat-expansion-panel mat-expansion-panel-header{padding:0 12px;height:40px;background:color-mix(in srgb,#000 55%,transparent)}::ng-deep .layer-selector-body .item-list .group mat-expansion-panel mat-expansion-panel-header:hover{background:color-mix(in srgb,#000 60%,transparent)!important}::ng-deep .layer-selector-body .item-list .group mat-expansion-panel mat-expansion-panel-header mat-panel-title{align-items:center;gap:6px;font-weight:600;color:#fff;font-size:13px}::ng-deep .layer-selector-body .item-list .group mat-expansion-panel mat-expansion-panel-header mat-panel-title mat-icon{color:#fff;font-size:16px;width:16px;height:16px;transition:transform .2s ease}::ng-deep .layer-selector-body .item-list .group mat-expansion-panel mat-expansion-panel-header mat-panel-title mat-icon.lightbulb{color:#dfca0e}::ng-deep .layer-selector-body .item-list .group mat-expansion-panel mat-expansion-panel-header mat-panel-title mat-icon.power-on{color:#4caf50}::ng-deep .layer-selector-body .item-list .group mat-expansion-panel mat-expansion-panel-header mat-panel-title mat-icon.power-off{color:#f44336}::ng-deep .layer-selector-body .item-list .group mat-expansion-panel .mat-expansion-panel-content{background:color-mix(in srgb,#000 40%,transparent)}::ng-deep .layer-selector-body .item-list .group mat-expansion-panel .mat-expansion-panel-content .mat-expansion-panel-body{padding:4px 0}::ng-deep .layer-selector-body .item-list .group .item-list{padding:0;max-height:none}::ng-deep .layer-selector-body .item-list .group .item-list .item{display:flex;align-items:center;gap:6px;padding:8px 12px;background:transparent;transition:all .2s ease;color:#fff;cursor:grab}::ng-deep .layer-selector-body .item-list .group .item-list .item:last-child{border-bottom:none}::ng-deep .layer-selector-body .item-list .group .item-list .item:hover{background-color:#0000004d;transition:all .2s ease}::ng-deep .layer-selector-body .item-list .group .item-list .item.cdk-drag-preview{background:#fff;box-shadow:0 4px 12px #00000026;border-radius:4px}::ng-deep .layer-selector-body .item-list .group .item-list .item.cdk-drag-placeholder{background:#e3f2fd;opacity:.6}::ng-deep .layer-selector-body .item-list .group .item-list .item.cdk-drag-animating{transition:transform .25s cubic-bezier(0,0,.2,1)}::ng-deep .layer-selector-body .item-list .group .item-list .item mat-icon{flex-shrink:0}::ng-deep .layer-selector-body .item-list .group .item-list .item mat-icon.drag-indicator{color:#fff;font-size:16px;width:16px;height:16px;cursor:grab}::ng-deep .layer-selector-body .item-list .group .item-list .item mat-icon.power-on{color:#4caf50;font-size:18px;width:18px;height:18px;cursor:pointer;margin-left:auto;padding:3px;border-radius:3px}::ng-deep .layer-selector-body .item-list .group .item-list .item mat-icon.power-on:hover{background:#4caf5033}::ng-deep .layer-selector-body .item-list .group .item-list .item mat-icon.zoom-off{color:#f44336;font-size:16px;width:16px;height:16px}::ng-deep .layer-selector-body .item-list .group .item-list .item mat-icon.power-off{color:#f44336;font-size:18px;width:18px;height:18px;cursor:pointer;margin-left:auto;padding:3px;border-radius:3px}::ng-deep .layer-selector-body .item-list .group .item-list .item mat-icon.power-off:hover{background:#f4433633}::ng-deep .layer-selector-body .item-list .group .item-list .item>:nth-child(2){flex:1;font-size:13px;color:#fff;margin:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}::ng-deep .layer-selector-body .item-list .group .item-list .item input[type=range]{width:80px;height:4px;margin:0 4px;flex-shrink:0}::ng-deep .mat-expansion-indicator svg{fill:#fff!important}.cdk-drag-preview{box-sizing:border-box;border-radius:4px;box-shadow:0 5px 15px #00000026;background:#fff;padding:10px 12px}.cdk-drag-placeholder{opacity:.3}.cdk-drag-animating{transition:transform .25s cubic-bezier(0,0,.2,1)}.item-list.cdk-drop-list-dragging .item:not(.cdk-drag-placeholder){transition:transform .25s cubic-bezier(0,0,.2,1)}.power-on{color:#4caf50}.power-off{color:#f44336}.display-none{display:none}::ng-deep .layer-selector-body .item-list::-webkit-scrollbar{width:8px;border-radius:10px;border:5px solid transparent}::ng-deep .layer-selector-body .item-list::-webkit-scrollbar-thumb{background:#0000004d;border:5px solid transparent;border-radius:10px;background-clip:padding-box}::ng-deep .layer-selector-body .item-list::-webkit-scrollbar-thumb:hover{background:#0006;background-clip:padding-box;border:3px solid transparent}\n"], dependencies: [{ kind: "ngmodule", type: MatFormFieldModule }, { kind: "component", type: i3.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i3.MatLabel, selector: "mat-label" }, { kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i2.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i3$1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i3$1.RangeValueAccessor, selector: "input[type=range][formControlName],input[type=range][formControl],input[type=range][ngModel]" }, { kind: "directive", type: i3$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i3$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: DragDropModule }, { kind: "directive", type: i4.CdkDropList, selector: "[cdkDropList], cdk-drop-list", inputs: ["cdkDropListConnectedTo", "cdkDropListData", "cdkDropListOrientation", "id", "cdkDropListLockAxis", "cdkDropListDisabled", "cdkDropListSortingDisabled", "cdkDropListEnterPredicate", "cdkDropListSortPredicate", "cdkDropListAutoScrollDisabled", "cdkDropListAutoScrollStep", "cdkDropListElementContainer"], outputs: ["cdkDropListDropped", "cdkDropListEntered", "cdkDropListExited", "cdkDropListSorted"], exportAs: ["cdkDropList"] }, { kind: "directive", type: i4.CdkDrag, selector: "[cdkDrag]", inputs: ["cdkDragData", "cdkDragLockAxis", "cdkDragRootElement", "cdkDragBoundary", "cdkDragStartDelay", "cdkDragFreeDragPosition", "cdkDragDisabled", "cdkDragConstrainPosition", "cdkDragPreviewClass", "cdkDragPreviewContainer", "cdkDragScale"], outputs: ["cdkDragStarted", "cdkDragReleased", "cdkDragEnded", "cdkDragEntered", "cdkDragExited", "cdkDragDropped", "cdkDragMoved"], exportAs: ["cdkDrag"] }, { kind: "directive", type: i4.CdkDragHandle, selector: "[cdkDragHandle]", inputs: ["cdkDragHandleDisabled"] }, { kind: "ngmodule", type: MatExpansionModule }, { kind: "component", type: i5.MatExpansionPanel, selector: "mat-expansion-panel", inputs: ["hideToggle", "togglePosition"], outputs: ["afterExpand", "afterCollapse"], exportAs: ["matExpansionPanel"] }, { kind: "component", type: i5.MatExpansionPanelHeader, selector: "mat-expansion-panel-header", inputs: ["expandedHeight", "collapsedHeight", "tabIndex"] }, { kind: "directive", type: i5.MatExpansionPanelTitle, selector: "mat-panel-title" }, { kind: "ngmodule", type: MatInputModule }, { kind: "directive", type: i6.MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly", "disabledInteractive"], exportAs: ["matInput"] }] });
275
533
  }
276
534
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.15", ngImport: i0, type: LayerSelectorComponent, decorators: [{
277
535
  type: Component,
278
536
  args: [{ selector: 'lib-layer-selector', imports: [MatFormFieldModule, CommonModule, MatIconModule, FormsModule, DragDropModule,
279
- MatExpansionModule, MatInputModule], template: "<div #layerSelectorIcon class=\"ol-unselectable ol-control layer-selector-icon\">\n <mat-icon (click)=\"toggleLayerSelector()\">layers</mat-icon>\n</div>\n<div #layerSelectorBody [class.display-none]=\"!showSelector\" class=\"ol-unselectable ol-control layer-selector-body\">\n <div class=\"search-section\">\n <mat-form-field appearance=\"outline\" class=\"w-full\">\n <mat-label>Filtrer</mat-label>\n <input \n matInput \n type=\"text\" \n [(ngModel)]=\"searchText\" \n (ngModelChange)=\"filteredLayerGroups=setfilteredGroups()\"\n placeholder=\"Skriv for at filtrere...\"\n />\n </mat-form-field>\n <mat-icon (click)=\"clearSearchText()\">undo</mat-icon>\n </div>\n \n <div\n cdkDropList\n [cdkDropListData]=\"filteredLayerGroups\"\n (cdkDropListDropped)=\"dropGroup($event)\"\n class=\"item-list\">\n @for (group of filteredLayerGroups; track group.id; let gIndex = $index) {\n <div class=\"group\" cdkDrag cdkDragPreviewDisabled>\n <mat-expansion-panel [(expanded)]=\"group.expanded\">\n <mat-expansion-panel-header>\n <mat-panel-title>\n @if (group.expanded) {\n <mat-icon>arrow_upward</mat-icon>\n }\n @if (!group.expanded) {\n <mat-icon>arrow_downward</mat-icon> \n }\n {{ group.name }}\n </mat-panel-title>\n </mat-expansion-panel-header>\n <!-- This is only shown during drag -->\n <!-- <div *cdkDragPreview class=\"drag-preview\">\n {{ group.name }}\n </div> -->\n\n <!-- Placeholder to avoid jump -->\n <!-- <div *cdkDragPlaceholder class=\"drag-placeholder\"></div> -->\n\n <div\n cdkDropList\n [cdkDropListData]=\"group.layers\"\n (cdkDropListDropped)=\"dropLayer($event, group)\"\n class=\"item-list\">\n @for (layer of group.layers; track layer.id; let iIndex = $index) {\n <div class=\"item\" cdkDrag cdkDragPreviewDisabled>\n <mat-icon class=\"drag-indicator\">drag_indicator</mat-icon>\n <span>{{ layer.name }}</span>\n @if (layer.visible) {\n <mat-icon (click)=\"toggleLayer(layer)\" class=\"power-on\">power</mat-icon>(t\u00E6nd)\n }\n @if (!layer.visible) {\n <mat-icon (click)=\"toggleLayer(layer)\" class=\"power-off\">power</mat-icon>(sluk)\n }\n </div>\n }\n </div>\n </mat-expansion-panel>\n </div>\n }\n </div>\n</div>\n", styles: ["::ng-deep .layer-selector-icon{position:absolute;left:auto;right:4em;bottom:.5em;z-index:1000}::ng-deep .layer-selector-icon mat-icon{display:flex;justify-content:center;align-items:center;cursor:pointer;transition:all .3s ease;font-size:45px;width:50px;height:50px;background-color:#0004;border:1px solid rgba(255,255,255,.8);border-radius:8px;-webkit-text-fill-color:#334155;-webkit-text-stroke-width:.9px;-webkit-text-stroke-color:white}::ng-deep .layer-selector-icon mat-icon:hover{box-shadow:0 4px 12px #0003;color:#3e4b5e}::ng-deep .layer-selector-body{position:absolute;left:auto;right:4em;bottom:4em;z-index:1000;background:#fff;border-radius:8px;box-shadow:0 4px 20px #00000026;border:1px solid #e0e0e0;width:320px;max-height:500px;overflow:hidden;display:flex;flex-direction:column}::ng-deep .layer-selector-body .search-section{display:flex;align-items:center;gap:8px;padding:16px 16px 8px;background:#fff;border-bottom:1px solid #e0e0e0}::ng-deep .layer-selector-body .search-section mat-form-field{flex:1}::ng-deep .layer-selector-body .search-section mat-form-field .mat-form-field-outline{background:#fff}::ng-deep .layer-selector-body .search-section mat-form-field .mat-form-field-wrapper{padding-bottom:0}::ng-deep .layer-selector-body .search-section mat-form-field .mat-form-field-outline-thick{color:#1976d2}::ng-deep .layer-selector-body .search-section mat-form-field input{font-size:14px;padding:8px 0}::ng-deep .layer-selector-body .search-section mat-form-field .mat-form-field-label{color:#666;font-weight:500}::ng-deep .layer-selector-body .search-section mat-icon{color:#666;cursor:pointer;padding:8px;border-radius:4px;transition:all .2s ease;font-size:30px;width:20px;height:20px;display:flex;justify-content:center;align-items:end}::ng-deep .layer-selector-body .search-section mat-icon:hover{color:#334155}::ng-deep .layer-selector-body .item-list{flex:1;overflow-y:auto;padding:8px;max-height:400px}::ng-deep .layer-selector-body .item-list .group{margin-bottom:8px;border-radius:6px;overflow:hidden;box-shadow:0 -2px 2px #4868b20a,0 2px 2px #6a6f7517,0 1px 2px #4868b214}::ng-deep .layer-selector-body .item-list .group mat-expansion-panel{box-shadow:none!important;border-radius:0!important}::ng-deep .layer-selector-body .item-list .group mat-expansion-panel mat-expansion-panel-header{padding:0 16px;height:48px;background:#f8f9fa;border-bottom:1px solid #e0e0e0}::ng-deep .layer-selector-body .item-list .group mat-expansion-panel mat-expansion-panel-header:hover{background:#e6e7ec}::ng-deep .layer-selector-body .item-list .group mat-expansion-panel mat-expansion-panel-header mat-panel-title{align-items:center;gap:8px;font-weight:600;color:#333;font-size:14px}::ng-deep .layer-selector-body .item-list .group mat-expansion-panel mat-expansion-panel-header mat-panel-title mat-icon{color:#666;font-size:18px;width:18px;height:18px;transition:transform .2s ease}::ng-deep .layer-selector-body .item-list .group mat-expansion-panel .mat-expansion-panel-content{background:#fff}::ng-deep .layer-selector-body .item-list .group mat-expansion-panel .mat-expansion-panel-content .mat-expansion-panel-body{padding:8px 0}::ng-deep .layer-selector-body .item-list .group .item-list{padding:0;max-height:none}::ng-deep .layer-selector-body .item-list .group .item-list .item{display:flex;align-items:center;gap:8px;padding:12px 16px;border-bottom:1px solid #f5f5f5;background:#fff;transition:all .2s ease;cursor:move}::ng-deep .layer-selector-body .item-list .group .item-list .item:last-child{border-bottom:none}::ng-deep .layer-selector-body .item-list .group .item-list .item:hover{background:#f8f9fa}::ng-deep .layer-selector-body .item-list .group .item-list .item.cdk-drag-preview{background:#fff;box-shadow:0 4px 12px #00000026;border-radius:4px}::ng-deep .layer-selector-body .item-list .group .item-list .item.cdk-drag-placeholder{background:#e3f2fd;opacity:.6}::ng-deep .layer-selector-body .item-list .group .item-list .item.cdk-drag-animating{transition:transform .25s cubic-bezier(0,0,.2,1)}::ng-deep .layer-selector-body .item-list .group .item-list .item mat-icon.drag-indicator{color:#9e9e9e;font-size:18px;width:18px;height:18px;cursor:move}::ng-deep .layer-selector-body .item-list .group .item-list .item mat-icon.power-on{color:#4caf50;font-size:20px;width:20px;height:20px;cursor:pointer;margin-left:auto;padding:4px;border-radius:4px}::ng-deep .layer-selector-body .item-list .group .item-list .item mat-icon.power-on:hover{background:#e8f5e8}::ng-deep .layer-selector-body .item-list .group .item-list .item mat-icon.power-off{color:#f44336;font-size:20px;width:20px;height:20px;cursor:pointer;margin-left:auto;padding:4px;border-radius:4px}::ng-deep .layer-selector-body .item-list .group .item-list .item mat-icon.power-off:hover{background:#ffebee}::ng-deep .layer-selector-body .item-list .group .item-list .item>:nth-child(2){flex:1;font-size:14px;color:#333;margin:0}.cdk-drag-preview{box-sizing:border-box;border-radius:4px;box-shadow:0 5px 15px #00000026;background:#fff;padding:12px 16px}.cdk-drag-placeholder{opacity:.3}.cdk-drag-animating{transition:transform .25s cubic-bezier(0,0,.2,1)}.item-list.cdk-drop-list-dragging .item:not(.cdk-drag-placeholder){transition:transform .25s cubic-bezier(0,0,.2,1)}.power-on{color:#4caf50}.power-off{color:#f44336}.display-none{display:none}::ng-deep .layer-selector-body .item-list{scrollbar-width:thin;scrollbar-color:#c1c1c1 transparent}\n"] }]
537
+ MatExpansionModule, MatInputModule], template: "<div #layerSelectorIcon class=\"ol-unselectable ol-control layer-selector-icon\">\n <mat-icon (click)=\"toggleLayerSelector()\">layers</mat-icon>\n</div>\n<div #layerSelectorBody [class.display-none]=\"!showSelector\" class=\"layer-selector-body-wrapper\" cdkDrag cdkDragBoundary=\".map-container\">\n <div class=\"drag-handle-selector\" cdkDragHandle>\n <mat-icon>drag_indicator</mat-icon>\n </div>\n <div class=\"ol-unselectable ol-control layer-selector-body\">\n <div class=\"search-section\">\n <mat-form-field appearance=\"outline\" class=\"w-full\">\n <mat-label>Filtrer</mat-label>\n <input \n matInput \n type=\"text\" \n [(ngModel)]=\"searchText\" \n (ngModelChange)=\"filteredLayerGroups=setfilteredGroups()\"\n placeholder=\"Skriv for at filtrere...\"\n />\n </mat-form-field>\n <mat-icon (click)=\"clearSearchText()\">undo</mat-icon>\n </div>\n \n <div\n cdkDropList\n [cdkDropListData]=\"filteredLayerGroups\"\n (cdkDropListDropped)=\"dropGroup($event)\"\n class=\"item-list\">\n @for (group of filteredLayerGroups; track group.id; let gIndex = $index) {\n <div class=\"group\" cdkDrag cdkDragPreviewDisabled>\n <mat-expansion-panel [(expanded)]=\"group.expanded\">\n <mat-expansion-panel-header>\n <mat-panel-title>\n @if (group.expanded) {\n <mat-icon>arrow_upward</mat-icon>\n }\n @if (!group.expanded) {\n <mat-icon>arrow_downward</mat-icon> \n }\n {{ group.name }} \n <mat-icon class=\"lightbulb\">lightbulb</mat-icon>\n ({{ group.noOfVisibleLayers }}/{{ group.layers.length }})\n @if (group.visible) {\n <mat-icon (click)=\"toggleGroup($event, group)\" class=\"power-on\">power</mat-icon>(t\u00E6nd)\n }\n @if (!group.visible) {\n <mat-icon (click)=\"toggleGroup($event, group)\" class=\"power-off\">power_off</mat-icon>(sluk)\n }\n </mat-panel-title>\n </mat-expansion-panel-header>\n <!-- This is only shown during drag -->\n <!-- <div *cdkDragPreview class=\"drag-preview\">\n {{ group.name }}\n </div> -->\n\n <!-- Placeholder to avoid jump -->\n <!-- <div *cdkDragPlaceholder class=\"drag-placeholder\"></div> -->\n\n <div\n cdkDropList\n [cdkDropListData]=\"group.layers\"\n (cdkDropListDropped)=\"dropLayer($event, group)\"\n class=\"item-list\">\n @for (layer of group.layers; track layer.id; let iIndex = $index) {\n <div class=\"item\" cdkDrag cdkDragPreviewDisabled>\n <mat-icon class=\"drag-indicator\">drag_indicator</mat-icon>\n <span>{{ layer.name }}</span>\n @if (layer.maxZoom < currentZoomLevel || layer.minZoom > currentZoomLevel) {\n <mat-icon class=\"zoom-off\">browser_not_supported</mat-icon>(zoom)\n }\n @if (layer.hasErrors) {\n <mat-icon class=\"zoom-off\">wifi_off</mat-icon>(fejle)\n }\n @if (layer.visible) {\n <mat-icon (click)=\"toggleLayer(layer)\" class=\"power-on\">power</mat-icon>(t\u00E6nd)\n }\n @if (!layer.visible) {\n <mat-icon (click)=\"toggleLayer(layer)\" class=\"power-off\">power_off</mat-icon>(sluk)\n }\n <input \n type=\"range\"\n min=\"0\"\n max=\"1\"\n step=\"0.05\"\n [(ngModel)]=\"layer.opacity\"\n (input)=\"updateOpacity(layer)\"\n (mousedown)=\"stopDrag($event)\"\n (touchstart)=\"stopDrag($event)\"\n (pointerdown)=\"stopDrag($event)\"\n >\n </div>\n }\n </div>\n </mat-expansion-panel>\n </div>\n }\n </div>\n </div>\n</div>", styles: ["::ng-deep .layer-selector-icon{position:absolute;left:auto;right:4em;bottom:.5em;z-index:1000}::ng-deep .layer-selector-icon mat-icon{display:flex;justify-content:center;align-items:center;cursor:pointer;transition:all .3s ease;outline:none;border:0;height:40px;border-radius:5px;width:40px;background:#fff;box-shadow:#0000004d 0 1px 4px -1px;font-size:2em;color:#666}::ng-deep .layer-selector-icon mat-icon:hover{box-shadow:0 4px 12px #0003;color:#3e4b5e}.layer-selector-body-wrapper{position:absolute;left:auto;right:4em;bottom:4em;z-index:1000;cursor:grab}.layer-selector-body-wrapper.cdk-drag-dragging{opacity:.8;cursor:grab;z-index:1001}.layer-selector-body-wrapper .ol-control{border-radius:0}.drag-handle-selector{display:flex;align-items:center;justify-content:center;background:color-mix(in srgb,#000 40%,transparent);border-radius:4px 4px 0 0;padding:4px;cursor:grab;box-shadow:0 -2px 8px #0003}.drag-handle-selector mat-icon{color:#fff;font-size:20px;width:20px;height:20px}::ng-deep .layer-selector-body{position:relative;left:auto;right:auto;bottom:auto;z-index:auto;background:color-mix(in srgb,#000 40%,transparent);box-shadow:0 4px 20px #00000026;width:480px;max-height:500px;overflow:hidden;display:flex;flex-direction:column}::ng-deep .layer-selector-body .search-section{display:flex;align-items:center;gap:6px;padding:8px 12px 6px}::ng-deep .layer-selector-body .search-section mat-form-field{flex:1}::ng-deep .layer-selector-body .search-section mat-form-field .mat-mdc-text-field-wrapper{padding-bottom:0}::ng-deep .layer-selector-body .search-section mat-form-field .mat-form-field-wrapper{padding-bottom:0;margin-bottom:0}::ng-deep .layer-selector-body .search-section mat-form-field .mat-mdc-form-field-infix{padding-bottom:6px;min-height:auto}::ng-deep .layer-selector-body .search-section mat-form-field .mat-form-field-outline{background:#fff}::ng-deep .layer-selector-body .search-section mat-form-field .mat-form-field-outline-thick{color:#1976d2}::ng-deep .layer-selector-body .search-section mat-form-field input{font-size:13px;padding:3px 0}::ng-deep .layer-selector-body .search-section mat-form-field .mat-form-field-label{color:#fff;font-weight:500;font-size:13px}::ng-deep .layer-selector-body .search-section mat-form-field .mat-mdc-form-field-subscript-wrapper{height:0;margin-top:0}::ng-deep .layer-selector-body .search-section mat-icon{color:#fff;cursor:pointer;padding:6px;border-radius:4px;transition:all .2s ease;font-size:24px;width:24px;height:24px;display:flex;justify-content:center;align-items:center}::ng-deep .layer-selector-body .search-section mat-icon:hover{color:#f9fafb}::ng-deep .layer-selector-body .item-list{flex:1;overflow-y:auto;padding:6px;max-height:400px}::ng-deep .layer-selector-body .item-list .group{margin-bottom:6px;overflow:hidden;box-shadow:0 -2px 2px #4868b20a,0 2px 2px #6a6f7517,0 1px 2px #4868b214}::ng-deep .layer-selector-body .item-list .group mat-expansion-panel{box-shadow:none!important;border-radius:0!important}::ng-deep .layer-selector-body .item-list .group mat-expansion-panel mat-expansion-panel-header{padding:0 12px;height:40px;background:color-mix(in srgb,#000 55%,transparent)}::ng-deep .layer-selector-body .item-list .group mat-expansion-panel mat-expansion-panel-header:hover{background:color-mix(in srgb,#000 60%,transparent)!important}::ng-deep .layer-selector-body .item-list .group mat-expansion-panel mat-expansion-panel-header mat-panel-title{align-items:center;gap:6px;font-weight:600;color:#fff;font-size:13px}::ng-deep .layer-selector-body .item-list .group mat-expansion-panel mat-expansion-panel-header mat-panel-title mat-icon{color:#fff;font-size:16px;width:16px;height:16px;transition:transform .2s ease}::ng-deep .layer-selector-body .item-list .group mat-expansion-panel mat-expansion-panel-header mat-panel-title mat-icon.lightbulb{color:#dfca0e}::ng-deep .layer-selector-body .item-list .group mat-expansion-panel mat-expansion-panel-header mat-panel-title mat-icon.power-on{color:#4caf50}::ng-deep .layer-selector-body .item-list .group mat-expansion-panel mat-expansion-panel-header mat-panel-title mat-icon.power-off{color:#f44336}::ng-deep .layer-selector-body .item-list .group mat-expansion-panel .mat-expansion-panel-content{background:color-mix(in srgb,#000 40%,transparent)}::ng-deep .layer-selector-body .item-list .group mat-expansion-panel .mat-expansion-panel-content .mat-expansion-panel-body{padding:4px 0}::ng-deep .layer-selector-body .item-list .group .item-list{padding:0;max-height:none}::ng-deep .layer-selector-body .item-list .group .item-list .item{display:flex;align-items:center;gap:6px;padding:8px 12px;background:transparent;transition:all .2s ease;color:#fff;cursor:grab}::ng-deep .layer-selector-body .item-list .group .item-list .item:last-child{border-bottom:none}::ng-deep .layer-selector-body .item-list .group .item-list .item:hover{background-color:#0000004d;transition:all .2s ease}::ng-deep .layer-selector-body .item-list .group .item-list .item.cdk-drag-preview{background:#fff;box-shadow:0 4px 12px #00000026;border-radius:4px}::ng-deep .layer-selector-body .item-list .group .item-list .item.cdk-drag-placeholder{background:#e3f2fd;opacity:.6}::ng-deep .layer-selector-body .item-list .group .item-list .item.cdk-drag-animating{transition:transform .25s cubic-bezier(0,0,.2,1)}::ng-deep .layer-selector-body .item-list .group .item-list .item mat-icon{flex-shrink:0}::ng-deep .layer-selector-body .item-list .group .item-list .item mat-icon.drag-indicator{color:#fff;font-size:16px;width:16px;height:16px;cursor:grab}::ng-deep .layer-selector-body .item-list .group .item-list .item mat-icon.power-on{color:#4caf50;font-size:18px;width:18px;height:18px;cursor:pointer;margin-left:auto;padding:3px;border-radius:3px}::ng-deep .layer-selector-body .item-list .group .item-list .item mat-icon.power-on:hover{background:#4caf5033}::ng-deep .layer-selector-body .item-list .group .item-list .item mat-icon.zoom-off{color:#f44336;font-size:16px;width:16px;height:16px}::ng-deep .layer-selector-body .item-list .group .item-list .item mat-icon.power-off{color:#f44336;font-size:18px;width:18px;height:18px;cursor:pointer;margin-left:auto;padding:3px;border-radius:3px}::ng-deep .layer-selector-body .item-list .group .item-list .item mat-icon.power-off:hover{background:#f4433633}::ng-deep .layer-selector-body .item-list .group .item-list .item>:nth-child(2){flex:1;font-size:13px;color:#fff;margin:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}::ng-deep .layer-selector-body .item-list .group .item-list .item input[type=range]{width:80px;height:4px;margin:0 4px;flex-shrink:0}::ng-deep .mat-expansion-indicator svg{fill:#fff!important}.cdk-drag-preview{box-sizing:border-box;border-radius:4px;box-shadow:0 5px 15px #00000026;background:#fff;padding:10px 12px}.cdk-drag-placeholder{opacity:.3}.cdk-drag-animating{transition:transform .25s cubic-bezier(0,0,.2,1)}.item-list.cdk-drop-list-dragging .item:not(.cdk-drag-placeholder){transition:transform .25s cubic-bezier(0,0,.2,1)}.power-on{color:#4caf50}.power-off{color:#f44336}.display-none{display:none}::ng-deep .layer-selector-body .item-list::-webkit-scrollbar{width:8px;border-radius:10px;border:5px solid transparent}::ng-deep .layer-selector-body .item-list::-webkit-scrollbar-thumb{background:#0000004d;border:5px solid transparent;border-radius:10px;background-clip:padding-box}::ng-deep .layer-selector-body .item-list::-webkit-scrollbar-thumb:hover{background:#0006;background-clip:padding-box;border:3px solid transparent}\n"] }]
280
538
  }], propDecorators: { contentIcon: [{
281
539
  type: ViewChild,
282
540
  args: ['layerSelectorIcon', { static: false }]
@@ -287,40 +545,57 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.15", ngImpo
287
545
  type: Input
288
546
  }], profile: [{
289
547
  type: Input
548
+ }], currentZoomLevel: [{
549
+ type: Input
290
550
  }] } });
291
551
 
552
+ // interface namedStyle {
553
+ // styleName: string;
554
+ // sldFeatureStyle: any;
555
+ // }
292
556
  class KomponentSettingsHelperService {
293
557
  _httpClient = inject(HttpClient);
294
- loadThem(settings, workpace, geoserver, map) {
295
- this.getStyleLoader(settings.style, workpace, geoserver).subscribe({
296
- next: loadedStyle => {
297
- const parser = new WKT();
298
- const features = settings.geometries.map(g => parser.readFeature(g.geometry, { featureProjection: 'EPSG:25832', dataProjection: 'EPSG:25832' }));
299
- console.log("🚀 ~ KomponentSettingsHelperService ~ loadThem ~ SLDReader:", SLDReader);
300
- debugger;
301
- const sldObject = SLDReader.Reader(loadedStyle);
302
- const sldLayer = SLDReader.getLayer(sldObject);
303
- const sldStyles = SLDReader.getStyle(sldLayer);
304
- const sldFeatureStyle = sldStyles.featuretypestyles[0];
305
- const olStyle = SLDReader.createOlStyle(sldFeatureStyle.rules[0], 'Polygon');
306
- const layer = new ol_layer_Vector({
307
- source: new VectorSource(),
308
- style: olStyle
309
- });
310
- layer.getSource()?.addFeatures(features.filter(f => !!f));
311
- map.addLayer(layer);
312
- const extent = features.filter(f => !!f).reduce((e, f) => extend(e, f.getGeometry().getExtent()), features[0]?.getGeometry().getExtent());
313
- map.getView().fit(extent);
314
- console.log("🚀 ~ KomponentSettingsHelperService ~ loadThem ~ style:", sldObject);
315
- }
316
- });
558
+ styles = {};
559
+ // loadNamedStyleObjects(styleNames: string[], workspace: string, geoserver: GeoServer): Observable<namedStyle[]> {
560
+ // const headerstring = `${geoserver.userName}:${geoserver.password}`;
561
+ // const encoded = btoa(headerstring);
562
+ // const headers = new HttpHeaders().set('Authorization', `Basic ${encoded}`);
563
+ // const result$ = styleNames.map(styleName =>
564
+ // this._httpClient.get(`${geoserver.url}/rest/workspaces/${workspace}/styles/${styleName}.sld`, { headers, responseType: 'text' })
565
+ // .pipe(map(loadedStyle => {
566
+ // const sldObject = SLDReader.Reader(loadedStyle);
567
+ // const sldLayer = SLDReader.getLayer(sldObject);
568
+ // const sldStyles = SLDReader.getStyle(sldLayer);
569
+ // const sldFeatureStyle = sldStyles.featuretypestyles[0];
570
+ // return { styleName, sldFeatureStyle } as namedStyle
571
+ // })));
572
+ // return combineLatest(result$);
573
+ // }
574
+ _getSldStyleObject$(styleName, workspace, geoserver) {
575
+ return this.styles[styleName] ? of(this.styles[styleName]) :
576
+ this._loadStyleFromGeoServer$(styleName, workspace, geoserver)
577
+ .pipe(tap(loadedStyle => {
578
+ this.styles[styleName] = loadedStyle;
579
+ }));
317
580
  }
318
- getStyleLoader(styleName, workspace, geoserver) {
581
+ _loadStyleFromGeoServer$(styleName, workspace, geoserver) {
319
582
  const headerstring = `${geoserver.userName}:${geoserver.password}`;
320
583
  const encoded = btoa(headerstring);
321
584
  const headers = new HttpHeaders().set('Authorization', `Basic ${encoded}`);
322
585
  const url = `${geoserver.url}/rest/workspaces/${workspace}/styles/${styleName}.sld`;
323
- return this._httpClient.get(url, { headers, responseType: 'text' });
586
+ return this._httpClient.get(url, { headers, responseType: 'text' }).pipe(map(loadedStyle => {
587
+ const sldObject = SLDReader.Reader(loadedStyle);
588
+ const sldLayer = SLDReader.getLayer(sldObject);
589
+ const sldStyles = SLDReader.getStyle(sldLayer);
590
+ const sldFeatureStyle = sldStyles.featuretypestyles[0];
591
+ return sldFeatureStyle;
592
+ }));
593
+ }
594
+ getStyle(styleName, workspace, geoserver, styleType) {
595
+ return this._getSldStyleObject$(styleName, workspace, geoserver)
596
+ .pipe(map(loadedStyle => {
597
+ return SLDReader.createOlStyle(loadedStyle.rules[0], styleType);
598
+ }));
324
599
  }
325
600
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.15", ngImport: i0, type: KomponentSettingsHelperService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
326
601
  static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.15", ngImport: i0, type: KomponentSettingsHelperService, providedIn: 'root' });
@@ -332,12 +607,720 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.15", ngImpo
332
607
  }]
333
608
  }] });
334
609
 
610
+ class DrawLayerSourceService {
611
+ _features = new Subject;
612
+ features$ = this._features.asObservable();
613
+ source;
614
+ _disabled = false;
615
+ constructor() {
616
+ this.source = new VectorSource();
617
+ this._initListener();
618
+ }
619
+ _initListener() {
620
+ this.source.on(['addfeature', 'changefeature', 'removefeature'], evt => {
621
+ if (this._disabled) {
622
+ return;
623
+ }
624
+ const features = this.source.getFeatures();
625
+ this._features.next(features);
626
+ });
627
+ }
628
+ get allFeatures() {
629
+ return this.source.getFeatures().map(f => {
630
+ // I want a static copy of the features.
631
+ const newFeature = f.clone();
632
+ // But cloning doesn't include the id and that is used for referencing a few places.
633
+ newFeature.setId(f.getId());
634
+ return newFeature;
635
+ });
636
+ }
637
+ remove(id) {
638
+ const feature = this.source.getFeatureById(id);
639
+ if (feature) {
640
+ this.source.removeFeature(feature);
641
+ }
642
+ }
643
+ setFeatures(features) {
644
+ this._disabled = true;
645
+ this.source.clear();
646
+ this.source.addFeatures(features.map(f => {
647
+ const newFeature = f.clone();
648
+ newFeature.setId(f.getId());
649
+ return newFeature;
650
+ }));
651
+ this._features.next(features);
652
+ this._disabled = false;
653
+ }
654
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.15", ngImport: i0, type: DrawLayerSourceService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
655
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.15", ngImport: i0, type: DrawLayerSourceService, providedIn: 'root' });
656
+ }
657
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.15", ngImport: i0, type: DrawLayerSourceService, decorators: [{
658
+ type: Injectable,
659
+ args: [{
660
+ providedIn: 'root'
661
+ }]
662
+ }], ctorParameters: () => [] });
663
+
664
+ class FeatureHelperService {
665
+ FEATURE_TYPE_ID = 'TypeId';
666
+ FEATURE_LOCKED = 'LOCKED';
667
+ _idCounter = 0;
668
+ isLocked = (feature) => feature.get(this.FEATURE_LOCKED) === 'true';
669
+ lock(feature) { feature.set(this.FEATURE_LOCKED, 'true'); }
670
+ setTypeId(feature, typeId) {
671
+ feature.set(this.FEATURE_TYPE_ID, typeId);
672
+ }
673
+ typeId = (feature) => feature.get(this.FEATURE_TYPE_ID);
674
+ setId(feature) {
675
+ if (!feature.getId()) {
676
+ feature.setId(this._idCounter++);
677
+ }
678
+ }
679
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.15", ngImport: i0, type: FeatureHelperService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
680
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.15", ngImport: i0, type: FeatureHelperService, providedIn: 'root' });
681
+ }
682
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.15", ngImport: i0, type: FeatureHelperService, decorators: [{
683
+ type: Injectable,
684
+ args: [{
685
+ providedIn: 'root'
686
+ }]
687
+ }] });
688
+
689
+ class UndoRedoService {
690
+ _drawlayerSourceService;
691
+ constructor(_drawlayerSourceService) {
692
+ this._drawlayerSourceService = _drawlayerSourceService;
693
+ }
694
+ disable() {
695
+ this._enabled = false;
696
+ }
697
+ enable() {
698
+ this._enabled = true;
699
+ }
700
+ _steps = [];
701
+ _currentStep = 0;
702
+ _changing = false;
703
+ _enabled = true;
704
+ init() {
705
+ this._steps[0] = this._drawlayerSourceService.allFeatures;
706
+ // For every change, add the current state
707
+ this._drawlayerSourceService.features$.pipe(filter(() => this._enabled)).subscribe({
708
+ next: () => {
709
+ if (this._changing) {
710
+ this._changing = false;
711
+ return;
712
+ }
713
+ this._steps = [...this._steps.slice(0, this._currentStep + 1), this._drawlayerSourceService.allFeatures];
714
+ this._currentStep++;
715
+ }
716
+ });
717
+ }
718
+ get canUndo() {
719
+ return this._currentStep > 0;
720
+ }
721
+ get canRedo() {
722
+ return this._currentStep < this._steps.length - 1;
723
+ }
724
+ undo() {
725
+ if (this.canUndo) {
726
+ this._changing = true;
727
+ this._currentStep--;
728
+ const features = this._steps[this._currentStep];
729
+ this._drawlayerSourceService.setFeatures(features);
730
+ }
731
+ }
732
+ redo() {
733
+ if (this.canRedo) {
734
+ this._changing = true;
735
+ this._currentStep++;
736
+ const features = this._steps[this._currentStep];
737
+ this._drawlayerSourceService.setFeatures(features);
738
+ }
739
+ }
740
+ reset() {
741
+ this._changing = true;
742
+ if (this._currentStep === 0) {
743
+ return;
744
+ }
745
+ const features = this._steps[0];
746
+ this._drawlayerSourceService.setFeatures(features);
747
+ }
748
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.15", ngImport: i0, type: UndoRedoService, deps: [{ token: DrawLayerSourceService }], target: i0.ɵɵFactoryTarget.Injectable });
749
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.15", ngImport: i0, type: UndoRedoService, providedIn: 'root' });
750
+ }
751
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.15", ngImport: i0, type: UndoRedoService, decorators: [{
752
+ type: Injectable,
753
+ args: [{
754
+ providedIn: 'root'
755
+ }]
756
+ }], ctorParameters: () => [{ type: DrawLayerSourceService }] });
757
+
758
+ class GeometrySplitService {
759
+ geoJsonFormat = new GeoJSON();
760
+ _featureHelper = inject(FeatureHelperService);
761
+ /**
762
+ * Split polygons in a vector source using a buffered LineString.
763
+ * @param lineFeature The drawn LineString feature.
764
+ * @param vectorSource The source containing polygons to split.
765
+ * @param bufferMeters Width of the buffer in meters (default: 1).
766
+ */
767
+ splitPolygonsWithBufferedLine(lineFeature, vectorSource, bufferMeters = 1) {
768
+ const lineGeoJSON = this.geoJsonFormat.writeFeatureObject(lineFeature);
769
+ // the buffer functions uses 4326 and makes a mess if you don't convert the projection
770
+ lineGeoJSON.geometry.coordinates = lineGeoJSON.geometry.coordinates.map(c => proj4('EPSG:25832', 'EPSG:4326', c));
771
+ // Buffer the line to create a narrow polygon
772
+ const bufferedLine = buffer(lineGeoJSON, bufferMeters, { units: 'meters' });
773
+ // Move back to 25832
774
+ bufferedLine.geometry.coordinates = bufferedLine.geometry.coordinates.map(a => a.map(c => proj4('EPSG:4326', 'EPSG:25832', c)));
775
+ const overlappingFeatures = vectorSource.getFeatures().filter(f => f.getGeometry()?.intersectsExtent(lineFeature.getGeometry()?.getExtent()));
776
+ overlappingFeatures.forEach((feature) => {
777
+ const overlappingFeatureGeoJson = this.geoJsonFormat.writeFeatureObject(feature);
778
+ const features = featureCollection([overlappingFeatureGeoJson, bufferedLine]);
779
+ const clipped = difference(features);
780
+ // If the result is not a MultiPolygon, the polygon was not clipped in two. In that case, I will do nothing.
781
+ if (clipped && clipped.geometry.type === 'MultiPolygon') {
782
+ vectorSource.removeFeature(feature);
783
+ const multipolygon = this.geoJsonFormat.readGeometry(clipped.geometry);
784
+ const polygons = multipolygon.getPolygons();
785
+ const newFeatures = polygons.map(p => {
786
+ const newFeature = feature.clone();
787
+ newFeature.setGeometry(p);
788
+ this._featureHelper.setId(newFeature);
789
+ return newFeature;
790
+ });
791
+ vectorSource.addFeatures(newFeatures);
792
+ }
793
+ });
794
+ }
795
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.15", ngImport: i0, type: GeometrySplitService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
796
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.15", ngImport: i0, type: GeometrySplitService, providedIn: 'root' });
797
+ }
798
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.15", ngImport: i0, type: GeometrySplitService, decorators: [{
799
+ type: Injectable,
800
+ args: [{
801
+ providedIn: 'root'
802
+ }]
803
+ }] });
804
+
805
+ class ToolboxComponent {
806
+ // Inputs
807
+ map;
808
+ showMeasureDistance = true;
809
+ showMeasureArea = true;
810
+ collapsed = false;
811
+ settings;
812
+ profile;
813
+ WKTInputEnabled = false;
814
+ deleteEnabled = false;
815
+ // Injects
816
+ _snackbar = inject(MatSnackBar);
817
+ _settingsHelper = inject(KomponentSettingsHelperService);
818
+ _drawLayerService = inject(DrawLayerSourceService);
819
+ _featureHelper = inject(FeatureHelperService);
820
+ _undoRedo = inject(UndoRedoService);
821
+ _geomSplitService = inject(GeometrySplitService);
822
+ selectedGeometrySetting = undefined;
823
+ showInputWKT = false;
824
+ WKTString;
825
+ activateShowInputWKT() {
826
+ this._clearAllInteractions();
827
+ this.showInputWKT = true;
828
+ }
829
+ cancelWKT() {
830
+ this._clearAllInteractions();
831
+ this.showInputWKT = false;
832
+ }
833
+ settingsChanged(evt) {
834
+ this._clearAllInteractions();
835
+ }
836
+ _activeFeaturesSource = new VectorSource();
837
+ _activeArea;
838
+ undo() {
839
+ this._undoRedo.undo();
840
+ }
841
+ redo() {
842
+ this._undoRedo.redo();
843
+ }
844
+ ReadWKT() {
845
+ this._clearAllInteractions();
846
+ this._setupActiveLayer();
847
+ if (!this.WKTString) {
848
+ return;
849
+ }
850
+ const wktFormat = new WKT();
851
+ try {
852
+ const feature = wktFormat.readFeature(this.WKTString, { featureProjection: 'EPSG:25832', dataProjection: 'EPSG:25832' });
853
+ const featureType = feature.getGeometry()?.getType();
854
+ if (!this.selectedGeometrySetting?.availableGeometryTypes?.some(g => g === featureType)) {
855
+ this._snackbar.open(`Den WKT du indlæser er af typen ${featureType} og det er ikke tilladt for denne indstilling`, '', { duration: 4000 });
856
+ return;
857
+ }
858
+ this._settingsHelper.getStyle(this.selectedGeometrySetting.style, this.profile.styleRepositoryWorkspace, this.profile.styleRepositoryGeoserver, featureType).subscribe({
859
+ next: featureStyle => {
860
+ feature.setStyle(featureStyle);
861
+ this._featureHelper.setTypeId(feature, this.selectedGeometrySetting?.typeId || '');
862
+ this._activeFeaturesSource.addFeature(feature);
863
+ const extent = buffer$1(feature.getGeometry().getExtent(), 10);
864
+ this.map.getView().fit(extent);
865
+ }
866
+ });
867
+ }
868
+ catch (ex) {
869
+ this._snackbar.open(`WKT kunne ikke indlæses - den er muligvis ikke formatteret korrekt`, '', { duration: 4000 });
870
+ }
871
+ }
872
+ _drawLayer;
873
+ _drawPolygon = new Draw({
874
+ type: 'Polygon',
875
+ source: this._drawLayerService.source
876
+ });
877
+ _actionSource = new VectorSource();
878
+ clipHole() {
879
+ this._clearAllInteractions();
880
+ const clipHoleDraw = new Draw({
881
+ source: this._actionSource,
882
+ type: 'Polygon'
883
+ });
884
+ this._setAsTemp(clipHoleDraw);
885
+ clipHoleDraw.on('drawend', evt => {
886
+ const overlappingFeatures = this._drawLayerService.source.getFeatures().filter(f => f.getGeometry()?.intersectsExtent(evt.feature.getGeometry().getExtent()));
887
+ const geoJsonFormat = new GeoJSON();
888
+ const holeGeoJson = geoJsonFormat.writeFeatureObject(evt.feature);
889
+ overlappingFeatures.forEach(f => {
890
+ const overlappingFeatureGeoJson = geoJsonFormat.writeFeatureObject(f);
891
+ // because the difference function takes featurecollection, I add the two features to a collection
892
+ // I have to cast this as any, because the featurecollection needs to be of Polygon or MultiPoly and it
893
+ // doesnt resolve to that.
894
+ const features = featureCollection([overlappingFeatureGeoJson, holeGeoJson]);
895
+ const clipped = difference(features);
896
+ if (clipped) {
897
+ const geom = geoJsonFormat.readGeometry(clipped.geometry);
898
+ f.setGeometry(geom);
899
+ }
900
+ });
901
+ });
902
+ this.map.addInteraction(clipHoleDraw);
903
+ }
904
+ split() {
905
+ this._clearAllInteractions();
906
+ const splitInteraction = new Draw({
907
+ type: 'LineString',
908
+ source: this._actionSource
909
+ });
910
+ this._setAsTemp(splitInteraction);
911
+ splitInteraction.on('drawend', evt => {
912
+ this._geomSplitService.splitPolygonsWithBufferedLine(evt.feature, this._drawLayerService.source, 0.01);
913
+ });
914
+ this.map.addInteraction(splitInteraction);
915
+ }
916
+ startDrawPoint() {
917
+ this._startDraw('Point');
918
+ }
919
+ startDrawLineString() {
920
+ this._startDraw('LineString');
921
+ }
922
+ startDrawPolygon() {
923
+ this._startDraw('Polygon');
924
+ }
925
+ _deleteSelect;
926
+ get deleteSelect() {
927
+ if (this._deleteSelect)
928
+ return this._deleteSelect;
929
+ this._deleteSelect = new Select({
930
+ layers: [this._drawLayer],
931
+ style: null, // ABSOLUTELY leave this as null! If not, select fires a change, because it applies a change of styling. LEAVE IT!
932
+ filter: feature => !this._featureHelper.isLocked(feature)
933
+ });
934
+ this._setAsTemp(this.deleteSelect);
935
+ this.deleteSelect.on('select', evt => {
936
+ this._drawLayerService.source.removeFeatures(evt.selected);
937
+ });
938
+ return this._deleteSelect;
939
+ }
940
+ _setAsTemp(interaction) {
941
+ interaction.set('temp', 'true');
942
+ }
943
+ _deleting = false;
944
+ startDelete() {
945
+ this._clearAllInteractions();
946
+ this._deleting = true;
947
+ this.map.addInteraction(this.deleteSelect);
948
+ }
949
+ startEditRemovePoints() {
950
+ this._clearAllInteractions();
951
+ const modifyRemove = new Modify({
952
+ source: this._drawLayerService.source,
953
+ // Make sure delete is allowed and other interactions are not.
954
+ deleteCondition: always,
955
+ insertVertexCondition: never
956
+ });
957
+ this._setAsTemp(modifyRemove);
958
+ this.map.addInteraction(modifyRemove);
959
+ }
960
+ startEdit() {
961
+ this._clearAllInteractions();
962
+ const modify = new Modify({
963
+ source: this._drawLayerService.source,
964
+ // No delete
965
+ deleteCondition: () => false,
966
+ // Allow moving existing and add new points
967
+ condition: always,
968
+ insertVertexCondition: always,
969
+ });
970
+ /* Modify fires changes to features a LOT, so to not flood the UndoRedo service, I disable until ending the modify
971
+ and then force an update, to add the updated feature to the undo-service. */
972
+ modify.on('modifystart', () => this._undoRedo.disable());
973
+ modify.on('modifyend', (evt) => { this._undoRedo.enable(); evt.features.forEach(f => f.set('updated', new Date().toISOString())); });
974
+ this._setAsTemp(modify);
975
+ this.map.addInteraction(modify);
976
+ }
977
+ _startDraw(geomType) {
978
+ this._clearAllInteractions();
979
+ if (!this.selectedGeometrySetting) {
980
+ return;
981
+ }
982
+ this._settingsHelper.getStyle(this.selectedGeometrySetting.style, this.profile.styleRepositoryWorkspace, this.profile.styleRepositoryGeoserver, geomType)
983
+ .subscribe({
984
+ next: style => {
985
+ const drawLineString = new Draw({
986
+ type: geomType,
987
+ source: this._drawLayerService.source,
988
+ style: style
989
+ });
990
+ drawLineString.on('drawend', evt => {
991
+ this._featureHelper.setTypeId(evt.feature, this.selectedGeometrySetting?.typeId || '');
992
+ this._featureHelper.setId(evt.feature);
993
+ evt.feature.setStyle(style);
994
+ });
995
+ this._setAsTemp(drawLineString);
996
+ this._setDrawLayer();
997
+ this.map.addInteraction(drawLineString);
998
+ this.map.getTargetElement().style.cursor = 'crosshair'; // Placeholder for now
999
+ }
1000
+ });
1001
+ }
1002
+ _clearDrawInteractions() {
1003
+ this.map.getTargetElement().style.cursor = '';
1004
+ this.map.getInteractions().forEach(i => {
1005
+ if (i.get('temp') === 'true') {
1006
+ this.map.removeInteraction(i);
1007
+ }
1008
+ });
1009
+ }
1010
+ compareGeometrySetting = (g1, g2) => g1 && g2 && g1.typeId === g2.typeId;
1011
+ _areaSource = new VectorSource();
1012
+ _areaLayer = new VectorLayer({
1013
+ source: this._areaSource,
1014
+ style: (feature) => {
1015
+ const geom = feature.getGeometry();
1016
+ const area = getArea(geom);
1017
+ const formattedArea = area > 1000000 ? `${(area / 1000000).toFixed(2)} km2` : `${area.toFixed(2)} m2`;
1018
+ return new Style({
1019
+ stroke: new Stroke({
1020
+ color: 'blue',
1021
+ width: 2
1022
+ }),
1023
+ fill: new Fill({
1024
+ color: 'grey'
1025
+ }),
1026
+ text: new Text({
1027
+ text: formattedArea,
1028
+ font: '12px Calibri,sans-serif',
1029
+ fill: new Fill({ color: '#000' }),
1030
+ stroke: new Stroke({ color: '#fff', width: 3 }),
1031
+ placement: 'line'
1032
+ })
1033
+ });
1034
+ }
1035
+ });
1036
+ _distanceLabelSource = new VectorSource();
1037
+ _distanceLabelLayer = new VectorLayer({
1038
+ source: this._distanceLabelSource,
1039
+ style: (feature) => {
1040
+ return new Style({
1041
+ text: new Text({
1042
+ text: feature.get('label'),
1043
+ font: '12px Calibri,sans-serif',
1044
+ fill: new Fill({ color: '#000' }),
1045
+ stroke: new Stroke({ color: '#fff', width: 3 })
1046
+ })
1047
+ });
1048
+ }
1049
+ });
1050
+ _distanceSource = new VectorSource();
1051
+ _distanceLayer = new VectorLayer({
1052
+ source: this._distanceSource,
1053
+ style: (feature) => {
1054
+ const geom = feature.getGeometry();
1055
+ const length = getLength(geom);
1056
+ const formattedLength = `${length.toFixed(2)} m`;
1057
+ return new Style({
1058
+ stroke: new Stroke({
1059
+ color: 'blue',
1060
+ width: 2
1061
+ }),
1062
+ text: new Text({
1063
+ text: formattedLength,
1064
+ font: '12px Calibri,sans-serif',
1065
+ fill: new Fill({ color: '#000' }),
1066
+ stroke: new Stroke({ color: '#fff', width: 3 }),
1067
+ placement: 'line'
1068
+ })
1069
+ });
1070
+ }
1071
+ });
1072
+ _distanceMeasureDraw = new Draw({
1073
+ type: 'LineString',
1074
+ source: this._distanceLayer.getSource(),
1075
+ });
1076
+ _areaMeasureDraw = new Draw({
1077
+ type: 'Polygon',
1078
+ source: this._areaSource
1079
+ });
1080
+ _active = 'No';
1081
+ _setupActiveLayer() {
1082
+ if (!this._activeArea) {
1083
+ this._activeArea = new VectorLayer({
1084
+ source: this._activeFeaturesSource
1085
+ });
1086
+ this.map.addLayer(this._activeArea);
1087
+ }
1088
+ }
1089
+ constructor() {
1090
+ // TODO: Move out from constructor
1091
+ // In order to add the distance to each segment of the distance measure feature, we need some extra handling like this.
1092
+ this._distanceMeasureDraw.on('drawstart', (evt) => {
1093
+ const sketch = evt.feature;
1094
+ sketch.getGeometry().on('change', (geomEvt) => {
1095
+ const geom = geomEvt.target;
1096
+ const coords = geom.getCoordinates();
1097
+ this._distanceLabelSource.clear(); // Clear existing labels for each change
1098
+ let totalLength = 0;
1099
+ for (let i = 0; i < coords.length - 1; i++) {
1100
+ const c1 = coords[i];
1101
+ const c2 = coords[i + 1];
1102
+ const segment = new LineString([c1, c2]);
1103
+ const length = getLength(segment);
1104
+ totalLength += length;
1105
+ const mid = [(c1[0] + c2[0]) / 2, (c1[1] + c2[1]) / 2];
1106
+ const formatted = `${length.toFixed(2)} m`;
1107
+ const labelFeature = new Feature({
1108
+ geometry: new Point(mid),
1109
+ label: formatted
1110
+ });
1111
+ this._distanceLabelSource.addFeature(labelFeature);
1112
+ }
1113
+ // Add total length at the last point
1114
+ if (coords.length > 1) {
1115
+ const endPoint = coords[coords.length - 1];
1116
+ const formattedTotal = `${totalLength.toFixed(2)} m`;
1117
+ const totalFeature = new Feature({
1118
+ geometry: new Point(endPoint),
1119
+ label: `Total: ${formattedTotal}`
1120
+ });
1121
+ this._distanceLabelSource.addFeature(totalFeature);
1122
+ }
1123
+ });
1124
+ });
1125
+ }
1126
+ _clearAllInteractions() {
1127
+ this.stopMeasureArea();
1128
+ this.stopMeasureLength();
1129
+ this._deleting = false;
1130
+ this._clearDrawInteractions();
1131
+ this._undoRedo.enable();
1132
+ }
1133
+ _setDrawLayer() {
1134
+ if (!this._drawLayer) {
1135
+ this._drawLayer = new VectorLayer({
1136
+ source: this._drawLayerService.source,
1137
+ // source: this._drawLayerSource,
1138
+ });
1139
+ this._drawLayer.set('DRAWLAYER', 'true');
1140
+ this.map.addLayer(this._drawLayer);
1141
+ this._setupMouseStyleCursor();
1142
+ }
1143
+ }
1144
+ ngOnInit() {
1145
+ if (this.settings.geometryTypeSettings && this.settings.geometryTypeSettings.length === 1) {
1146
+ this.selectedGeometrySetting = this.settings.geometryTypeSettings[0];
1147
+ }
1148
+ this._drawPolygon.on('drawend', evt => {
1149
+ this._featureHelper.setTypeId(evt.feature, this.selectedGeometrySetting?.typeId || '');
1150
+ });
1151
+ }
1152
+ _setupMouseStyleCursor() {
1153
+ this.map.on('pointermove', (evt) => {
1154
+ const pixel = this.map.getEventPixel(evt.originalEvent);
1155
+ const feature = this.map.forEachFeatureAtPixel(pixel, (f) => f, {
1156
+ layerFilter: (layer) => {
1157
+ return layer.get('DRAWLAYER') === 'true';
1158
+ }
1159
+ });
1160
+ const cursor = feature && this._deleting && !feature.get('LOCKED') ? 'crosshair' : ''; // TODO: Set actual style for delete
1161
+ this.map.getTargetElement().style.cursor = cursor;
1162
+ });
1163
+ }
1164
+ toggleCollapsed() {
1165
+ this.collapsed = !this.collapsed;
1166
+ if (this.collapsed) {
1167
+ this.stopMeasureArea();
1168
+ this.stopMeasureLength();
1169
+ }
1170
+ }
1171
+ startMeasureArea() {
1172
+ if (this._active === 'Area') {
1173
+ this.stopMeasureArea();
1174
+ this._active = 'No';
1175
+ return;
1176
+ }
1177
+ // this.geometryTypeSettings
1178
+ this._clearDrawInteractions();
1179
+ this.stopMeasureLength();
1180
+ this.map.addLayer(this._areaLayer);
1181
+ this.map.addInteraction(this._areaMeasureDraw);
1182
+ this._active = 'Area';
1183
+ }
1184
+ stopMeasureArea() {
1185
+ this.map.getTargetElement().style.cursor = '';
1186
+ this._areaSource.clear();
1187
+ this.map.removeInteraction(this._areaMeasureDraw);
1188
+ this.map.removeLayer(this._areaLayer);
1189
+ }
1190
+ stopMeasureLength() {
1191
+ this.map.getTargetElement().style.cursor = '';
1192
+ this._distanceSource.clear();
1193
+ this._distanceLabelSource.clear();
1194
+ this.map.removeInteraction(this._distanceMeasureDraw);
1195
+ this.map.removeLayer(this._distanceLayer);
1196
+ this.map.removeLayer(this._distanceLabelLayer);
1197
+ }
1198
+ startMeasureLength() {
1199
+ if (this._active === 'Distance') {
1200
+ this.stopMeasureLength();
1201
+ this._active = 'No';
1202
+ return;
1203
+ }
1204
+ this._clearAllInteractions();
1205
+ this.map.addLayer(this._distanceLayer);
1206
+ this.map.addLayer(this._distanceLabelLayer);
1207
+ this.map.addInteraction(this._distanceMeasureDraw);
1208
+ this._active = 'Distance';
1209
+ }
1210
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.15", ngImport: i0, type: ToolboxComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1211
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.15", type: ToolboxComponent, isStandalone: true, selector: "map-toolbox", inputs: { map: "map", showMeasureDistance: "showMeasureDistance", showMeasureArea: "showMeasureArea", collapsed: "collapsed", settings: "settings", profile: "profile", WKTInputEnabled: "WKTInputEnabled", deleteEnabled: "deleteEnabled" }, ngImport: i0, template: "<div class=\"toolbox-wrapper\" cdkDrag cdkDragBoundary=\".map-container\">\n <div class=\"drag-handle-toolbox\" cdkDragHandle>\n <mat-icon>drag_indicator</mat-icon>\n </div>\n <div class=\"toolbox-container\">\n <mat-icon class=\"toggle-icon\" (click)=\"toggleCollapsed()\">{{ collapsed ? 'expand_more' : 'expand_less' }}</mat-icon>\n @if(!collapsed) {\n <div class=\"toolbox-content\">\n @if (settings.geometryTypeSettings && settings.geometryTypeSettings.length) {\n <mat-select class=\"geometry-selector\" (selectionChange)=\"settingsChanged($event)\" [(ngModel)]=\"selectedGeometrySetting\" [compareWith]=\"compareGeometrySetting\">\n @for (setting of settings.geometryTypeSettings; track setting) {\n <mat-option [value]=\"setting\">{{setting.typeName}}</mat-option>\n }\n </mat-select> \n }\n <mat-icon class=\"compact-icon\" (click)=\"undo()\" matTooltip=\"Fortryd\" matTooltipPosition=\"right\">undo</mat-icon>\n <mat-icon class=\"compact-icon\" (click)=\"redo()\" matTooltip=\"Gentag\" matTooltipPosition=\"right\">redo</mat-icon>\n @if(settings.editEnabled) {\n <mat-icon class=\"compact-icon\" (click)=\"startEdit()\" matTooltip=\"Rediger\" matTooltipPosition=\"right\">edit_square</mat-icon>\n <mat-icon class=\"compact-icon\" (click)=\"startEditRemovePoints()\" matTooltip=\"Fjern punkter\" matTooltipPosition=\"right\">close</mat-icon>\n }\n @if(settings.cutHoleEnabled) {\n <mat-icon class=\"compact-icon\" (click)=\"clipHole()\" matTooltip=\"Klip hul\" matTooltipPosition=\"right\">restaurant</mat-icon>\n }\n @if(settings.splitEnabled) {\n <mat-icon class=\"compact-icon\" (click)=\"split()\" matTooltip=\"Sk\u00E6r over\" matTooltipPosition=\"right\">carpenter</mat-icon>\n }\n @if(selectedGeometrySetting && selectedGeometrySetting.availableGeometryTypes?.length) {\n <div class=\"geometry-tools\">\n @for(geomType of selectedGeometrySetting.availableGeometryTypes;track geomType) {\n @if(geomType === 'Polygon') {\n <button class=\"compact-button primary\" (click)=\"startDrawPolygon()\">Polygon</button>\n }\n @if(geomType === 'LineString') {\n <button class=\"compact-button primary\" (click)=\"startDrawLineString()\">LineString</button>\n }\n @if(geomType === 'Point') {\n <button class=\"compact-button primary\" (click)=\"startDrawPoint()\">Punkter</button>\n }\n }\n <button class=\"compact-button secondary\" (click)=\"activateShowInputWKT()\">WKT</button>\n </div>\n @if(showInputWKT) {\n <div class=\"wkt-section\">\n <input class=\"compact-input\" [(ngModel)]=\"WKTString\">\n <div class=\"wkt-actions\">\n <button class=\"compact-button primary\" (click)=\"ReadWKT()\">Indl\u00E6s WKT</button>\n <button class=\"compact-button\" (click)=\"cancelWKT()\">Annuller</button>\n </div>\n </div>\n }\n }\n <div class=\"measurement-tools\">\n @if (deleteEnabled) {\n <mat-icon class=\"compact-icon\" (click)=\"startDelete()\">delete</mat-icon>\n }\n @if (showMeasureDistance) {\n <mat-icon class=\"compact-icon\" (click)=\"startMeasureLength()\">straighten</mat-icon>\n }\n @if(showMeasureArea) {\n <mat-icon class=\"compact-icon\" (click)=\"startMeasureArea()\">square_foot</mat-icon>\n } \n </div>\n </div>\n }\n </div>\n</div>", styles: [".toolbox-wrapper{position:absolute;left:1em;top:17em;z-index:1000;cursor:grab}.toolbox-wrapper.cdk-drag-dragging{opacity:.8;cursor:grab;z-index:1001}.drag-handle-toolbox{display:flex;align-items:center;justify-content:center;background:#fff;border-radius:4px 4px 0 0;padding:4px;cursor:grab}.drag-handle-toolbox mat-icon{color:#546e7a;font-size:18px;width:18px;height:18px}:host{position:relative;display:flex;justify-content:center}:host.expanded{width:auto;min-width:220px;padding:12px}.toolbox-container{display:flex;flex-direction:column;align-items:center;width:100%;gap:10px;background:#fff;box-shadow:#0000004d 0 1px 4px -1px;padding:10px;width:auto;min-width:24px;transition:all .3s ease;cursor:default}.toggle-icon{cursor:pointer;color:#546e7a;transition:all .2s ease;-webkit-user-select:none;user-select:none;font-size:22px;width:22px;height:22px;display:flex;align-items:center;justify-content:center;border-radius:6px}.toggle-icon:hover{color:#1976d2;background:#1976d214}.toolbox-content{display:flex;flex-direction:column;width:100%;gap:10px;animation:slideDown .3s cubic-bezier(.4,0,.2,1)}.geometry-selector{width:100%}.geometry-selector ::ng-deep .mat-mdc-select{font-size:14px;line-height:1.4;border-radius:6px}.geometry-selector ::ng-deep .mat-mdc-select-trigger{height:32px;min-height:32px;padding:0 10px;border:1px solid #e0e0e0;border-radius:6px;background:#fff;transition:all .2s ease}.geometry-selector ::ng-deep .mat-mdc-select-trigger:hover{border-color:#1976d2;background:#fafbfc}.geometry-selector ::ng-deep .mat-mdc-select-value{font-size:14px;font-weight:500;color:#333}.geometry-selector ::ng-deep .mat-mdc-select-arrow-wrapper{height:16px;transform:scale(.85)}.geometry-selector ::ng-deep .mat-mdc-form-field-infix{min-height:32px;padding:6px 0}.geometry-selector ::ng-deep .mat-mdc-form-field-subscript-wrapper{display:none}::ng-deep .mat-mdc-select-panel{min-width:fit-content!important;max-width:320px!important;background:#fff!important;border:1px solid #e0e0e0!important;border-radius:8px!important;box-shadow:0 8px 24px #0000001f,0 2px 6px #00000014!important;margin-top:6px!important;padding:4px 0!important}::ng-deep .mat-mdc-select-panel .mat-mdc-option{font-size:14px!important;min-height:36px!important;padding:8px 14px!important;transition:all .15s ease!important}::ng-deep .mat-mdc-select-panel .mat-mdc-option:hover:not(.mat-mdc-option-disabled){background:#f5f7fa!important}::ng-deep .mat-mdc-select-panel .mat-mdc-option.mat-active{background:#e3f2fd!important;color:#1976d2!important}::ng-deep .mat-mdc-select-panel .mat-mdc-option .mdc-list-item__primary-text{font-size:14px!important;font-weight:500!important;color:#333!important}::ng-deep .cdk-overlay-pane{z-index:1001}::ng-deep .cdk-overlay-backdrop{z-index:1000}.geometry-tools{display:grid;grid-template-columns:1fr 1fr;gap:6px;width:100%}.measurement-tools{display:flex;gap:8px;justify-content:center;padding-top:10px;border-top:1px solid rgba(0,0,0,.06)}.wkt-section{display:flex;flex-direction:column;gap:8px;padding-top:10px;border-top:1px solid rgba(0,0,0,.06);animation:fadeIn .2s ease}.wkt-actions{display:flex;gap:6px;justify-content:space-between}.compact-button{padding:6px 10px;border:none;border-radius:6px;background:#f1f3f5;color:#495057;font-size:12px;font-weight:600;cursor:pointer;transition:all .2s cubic-bezier(.4,0,.2,1);text-transform:uppercase;letter-spacing:.4px;min-height:32px;display:flex;align-items:center;justify-content:center;flex:1;box-shadow:0 1px 2px #0000000d}.compact-button:hover{background:#e9ecef;box-shadow:0 2px 4px #0000001a}.compact-button.primary{background:linear-gradient(135deg,#1976d2,#1565c0);color:#fff;box-shadow:0 2px 4px #1976d233}.compact-button.primary:hover{background:linear-gradient(135deg,#1565c0,#0d47a1);box-shadow:0 3px 8px #1976d24d}.compact-button.secondary{background:#6c757d;color:#fff}.compact-button.secondary:hover{background:#5a6268}.compact-icon{cursor:pointer;color:#546e7a;transition:all .2s cubic-bezier(.4,0,.2,1);-webkit-user-select:none;user-select:none;width:32px;height:32px;display:flex;align-items:center;justify-content:center;border-radius:6px;background:#fff;border:1px solid #e0e0e0;box-shadow:0 1px 2px #0000000d}.compact-icon:hover{color:#1976d2;background:#e3f2fd;border-color:#1976d2;box-shadow:0 2px 4px #0000001a}.compact-icon:active{box-shadow:0 1px 2px #00000014}.compact-input{padding:8px 10px;border:1px solid #e0e0e0;border-radius:6px;font-size:13px;transition:all .2s ease;background:#fff;color:#333}.compact-input:focus{outline:none;border-color:#1976d2;background:#fafbfc;box-shadow:0 0 0 3px #1976d21a}@keyframes slideDown{0%{opacity:0}to{opacity:1}}@keyframes fadeIn{0%{opacity:0}to{opacity:1}}@media (max-width: 768px){.toolbox-wrapper{left:.5em;top:8em}:host{padding:8px;min-width:32px}:host.expanded{min-width:200px;padding:10px}.compact-button{font-size:11px;padding:5px 8px;min-height:28px}.compact-icon{width:28px;height:28px;font-size:18px}::ng-deep .mat-mdc-select-panel{max-width:280px!important}::ng-deep .mat-mdc-select-panel .mat-mdc-option{font-size:13px!important;min-height:32px!important;padding:6px 12px!important}}@media (max-width: 480px){.toolbox-wrapper{transform-origin:left top}.geometry-tools{gap:4px}.compact-button{font-size:10px;letter-spacing:.2px}}::ng-deep .mat-mdc-simple-snack-bar{background-color:#fff!important}\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i3$1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i3$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i3$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i2.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatOptionModule }, { kind: "component", type: i3$2.MatOption, selector: "mat-option", inputs: ["value", "id", "disabled"], outputs: ["onSelectionChange"], exportAs: ["matOption"] }, { kind: "ngmodule", type: MatSelectModule }, { kind: "component", type: i4$1.MatSelect, selector: "mat-select", inputs: ["aria-describedby", "panelClass", "disabled", "disableRipple", "tabIndex", "hideSingleSelectionIndicator", "placeholder", "required", "multiple", "disableOptionCentering", "compareWith", "value", "aria-label", "aria-labelledby", "errorStateMatcher", "typeaheadDebounceInterval", "sortComparator", "id", "panelWidth", "canSelectNullableOptions"], outputs: ["openedChange", "opened", "closed", "selectionChange", "valueChange"], exportAs: ["matSelect"] }, { kind: "ngmodule", type: DragDropModule }, { kind: "directive", type: i4.CdkDrag, selector: "[cdkDrag]", inputs: ["cdkDragData", "cdkDragLockAxis", "cdkDragRootElement", "cdkDragBoundary", "cdkDragStartDelay", "cdkDragFreeDragPosition", "cdkDragDisabled", "cdkDragConstrainPosition", "cdkDragPreviewClass", "cdkDragPreviewContainer", "cdkDragScale"], outputs: ["cdkDragStarted", "cdkDragReleased", "cdkDragEnded", "cdkDragEntered", "cdkDragExited", "cdkDragDropped", "cdkDragMoved"], exportAs: ["cdkDrag"] }, { kind: "directive", type: i4.CdkDragHandle, selector: "[cdkDragHandle]", inputs: ["cdkDragHandleDisabled"] }, { kind: "ngmodule", type: MatTooltipModule }, { kind: "directive", type: i6$1.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }] });
1212
+ }
1213
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.15", ngImport: i0, type: ToolboxComponent, decorators: [{
1214
+ type: Component,
1215
+ args: [{ selector: 'map-toolbox', imports: [FormsModule, CommonModule, MatIconModule, MatOptionModule, MatSelectModule, DragDropModule, MatTooltipModule], template: "<div class=\"toolbox-wrapper\" cdkDrag cdkDragBoundary=\".map-container\">\n <div class=\"drag-handle-toolbox\" cdkDragHandle>\n <mat-icon>drag_indicator</mat-icon>\n </div>\n <div class=\"toolbox-container\">\n <mat-icon class=\"toggle-icon\" (click)=\"toggleCollapsed()\">{{ collapsed ? 'expand_more' : 'expand_less' }}</mat-icon>\n @if(!collapsed) {\n <div class=\"toolbox-content\">\n @if (settings.geometryTypeSettings && settings.geometryTypeSettings.length) {\n <mat-select class=\"geometry-selector\" (selectionChange)=\"settingsChanged($event)\" [(ngModel)]=\"selectedGeometrySetting\" [compareWith]=\"compareGeometrySetting\">\n @for (setting of settings.geometryTypeSettings; track setting) {\n <mat-option [value]=\"setting\">{{setting.typeName}}</mat-option>\n }\n </mat-select> \n }\n <mat-icon class=\"compact-icon\" (click)=\"undo()\" matTooltip=\"Fortryd\" matTooltipPosition=\"right\">undo</mat-icon>\n <mat-icon class=\"compact-icon\" (click)=\"redo()\" matTooltip=\"Gentag\" matTooltipPosition=\"right\">redo</mat-icon>\n @if(settings.editEnabled) {\n <mat-icon class=\"compact-icon\" (click)=\"startEdit()\" matTooltip=\"Rediger\" matTooltipPosition=\"right\">edit_square</mat-icon>\n <mat-icon class=\"compact-icon\" (click)=\"startEditRemovePoints()\" matTooltip=\"Fjern punkter\" matTooltipPosition=\"right\">close</mat-icon>\n }\n @if(settings.cutHoleEnabled) {\n <mat-icon class=\"compact-icon\" (click)=\"clipHole()\" matTooltip=\"Klip hul\" matTooltipPosition=\"right\">restaurant</mat-icon>\n }\n @if(settings.splitEnabled) {\n <mat-icon class=\"compact-icon\" (click)=\"split()\" matTooltip=\"Sk\u00E6r over\" matTooltipPosition=\"right\">carpenter</mat-icon>\n }\n @if(selectedGeometrySetting && selectedGeometrySetting.availableGeometryTypes?.length) {\n <div class=\"geometry-tools\">\n @for(geomType of selectedGeometrySetting.availableGeometryTypes;track geomType) {\n @if(geomType === 'Polygon') {\n <button class=\"compact-button primary\" (click)=\"startDrawPolygon()\">Polygon</button>\n }\n @if(geomType === 'LineString') {\n <button class=\"compact-button primary\" (click)=\"startDrawLineString()\">LineString</button>\n }\n @if(geomType === 'Point') {\n <button class=\"compact-button primary\" (click)=\"startDrawPoint()\">Punkter</button>\n }\n }\n <button class=\"compact-button secondary\" (click)=\"activateShowInputWKT()\">WKT</button>\n </div>\n @if(showInputWKT) {\n <div class=\"wkt-section\">\n <input class=\"compact-input\" [(ngModel)]=\"WKTString\">\n <div class=\"wkt-actions\">\n <button class=\"compact-button primary\" (click)=\"ReadWKT()\">Indl\u00E6s WKT</button>\n <button class=\"compact-button\" (click)=\"cancelWKT()\">Annuller</button>\n </div>\n </div>\n }\n }\n <div class=\"measurement-tools\">\n @if (deleteEnabled) {\n <mat-icon class=\"compact-icon\" (click)=\"startDelete()\">delete</mat-icon>\n }\n @if (showMeasureDistance) {\n <mat-icon class=\"compact-icon\" (click)=\"startMeasureLength()\">straighten</mat-icon>\n }\n @if(showMeasureArea) {\n <mat-icon class=\"compact-icon\" (click)=\"startMeasureArea()\">square_foot</mat-icon>\n } \n </div>\n </div>\n }\n </div>\n</div>", styles: [".toolbox-wrapper{position:absolute;left:1em;top:17em;z-index:1000;cursor:grab}.toolbox-wrapper.cdk-drag-dragging{opacity:.8;cursor:grab;z-index:1001}.drag-handle-toolbox{display:flex;align-items:center;justify-content:center;background:#fff;border-radius:4px 4px 0 0;padding:4px;cursor:grab}.drag-handle-toolbox mat-icon{color:#546e7a;font-size:18px;width:18px;height:18px}:host{position:relative;display:flex;justify-content:center}:host.expanded{width:auto;min-width:220px;padding:12px}.toolbox-container{display:flex;flex-direction:column;align-items:center;width:100%;gap:10px;background:#fff;box-shadow:#0000004d 0 1px 4px -1px;padding:10px;width:auto;min-width:24px;transition:all .3s ease;cursor:default}.toggle-icon{cursor:pointer;color:#546e7a;transition:all .2s ease;-webkit-user-select:none;user-select:none;font-size:22px;width:22px;height:22px;display:flex;align-items:center;justify-content:center;border-radius:6px}.toggle-icon:hover{color:#1976d2;background:#1976d214}.toolbox-content{display:flex;flex-direction:column;width:100%;gap:10px;animation:slideDown .3s cubic-bezier(.4,0,.2,1)}.geometry-selector{width:100%}.geometry-selector ::ng-deep .mat-mdc-select{font-size:14px;line-height:1.4;border-radius:6px}.geometry-selector ::ng-deep .mat-mdc-select-trigger{height:32px;min-height:32px;padding:0 10px;border:1px solid #e0e0e0;border-radius:6px;background:#fff;transition:all .2s ease}.geometry-selector ::ng-deep .mat-mdc-select-trigger:hover{border-color:#1976d2;background:#fafbfc}.geometry-selector ::ng-deep .mat-mdc-select-value{font-size:14px;font-weight:500;color:#333}.geometry-selector ::ng-deep .mat-mdc-select-arrow-wrapper{height:16px;transform:scale(.85)}.geometry-selector ::ng-deep .mat-mdc-form-field-infix{min-height:32px;padding:6px 0}.geometry-selector ::ng-deep .mat-mdc-form-field-subscript-wrapper{display:none}::ng-deep .mat-mdc-select-panel{min-width:fit-content!important;max-width:320px!important;background:#fff!important;border:1px solid #e0e0e0!important;border-radius:8px!important;box-shadow:0 8px 24px #0000001f,0 2px 6px #00000014!important;margin-top:6px!important;padding:4px 0!important}::ng-deep .mat-mdc-select-panel .mat-mdc-option{font-size:14px!important;min-height:36px!important;padding:8px 14px!important;transition:all .15s ease!important}::ng-deep .mat-mdc-select-panel .mat-mdc-option:hover:not(.mat-mdc-option-disabled){background:#f5f7fa!important}::ng-deep .mat-mdc-select-panel .mat-mdc-option.mat-active{background:#e3f2fd!important;color:#1976d2!important}::ng-deep .mat-mdc-select-panel .mat-mdc-option .mdc-list-item__primary-text{font-size:14px!important;font-weight:500!important;color:#333!important}::ng-deep .cdk-overlay-pane{z-index:1001}::ng-deep .cdk-overlay-backdrop{z-index:1000}.geometry-tools{display:grid;grid-template-columns:1fr 1fr;gap:6px;width:100%}.measurement-tools{display:flex;gap:8px;justify-content:center;padding-top:10px;border-top:1px solid rgba(0,0,0,.06)}.wkt-section{display:flex;flex-direction:column;gap:8px;padding-top:10px;border-top:1px solid rgba(0,0,0,.06);animation:fadeIn .2s ease}.wkt-actions{display:flex;gap:6px;justify-content:space-between}.compact-button{padding:6px 10px;border:none;border-radius:6px;background:#f1f3f5;color:#495057;font-size:12px;font-weight:600;cursor:pointer;transition:all .2s cubic-bezier(.4,0,.2,1);text-transform:uppercase;letter-spacing:.4px;min-height:32px;display:flex;align-items:center;justify-content:center;flex:1;box-shadow:0 1px 2px #0000000d}.compact-button:hover{background:#e9ecef;box-shadow:0 2px 4px #0000001a}.compact-button.primary{background:linear-gradient(135deg,#1976d2,#1565c0);color:#fff;box-shadow:0 2px 4px #1976d233}.compact-button.primary:hover{background:linear-gradient(135deg,#1565c0,#0d47a1);box-shadow:0 3px 8px #1976d24d}.compact-button.secondary{background:#6c757d;color:#fff}.compact-button.secondary:hover{background:#5a6268}.compact-icon{cursor:pointer;color:#546e7a;transition:all .2s cubic-bezier(.4,0,.2,1);-webkit-user-select:none;user-select:none;width:32px;height:32px;display:flex;align-items:center;justify-content:center;border-radius:6px;background:#fff;border:1px solid #e0e0e0;box-shadow:0 1px 2px #0000000d}.compact-icon:hover{color:#1976d2;background:#e3f2fd;border-color:#1976d2;box-shadow:0 2px 4px #0000001a}.compact-icon:active{box-shadow:0 1px 2px #00000014}.compact-input{padding:8px 10px;border:1px solid #e0e0e0;border-radius:6px;font-size:13px;transition:all .2s ease;background:#fff;color:#333}.compact-input:focus{outline:none;border-color:#1976d2;background:#fafbfc;box-shadow:0 0 0 3px #1976d21a}@keyframes slideDown{0%{opacity:0}to{opacity:1}}@keyframes fadeIn{0%{opacity:0}to{opacity:1}}@media (max-width: 768px){.toolbox-wrapper{left:.5em;top:8em}:host{padding:8px;min-width:32px}:host.expanded{min-width:200px;padding:10px}.compact-button{font-size:11px;padding:5px 8px;min-height:28px}.compact-icon{width:28px;height:28px;font-size:18px}::ng-deep .mat-mdc-select-panel{max-width:280px!important}::ng-deep .mat-mdc-select-panel .mat-mdc-option{font-size:13px!important;min-height:32px!important;padding:6px 12px!important}}@media (max-width: 480px){.toolbox-wrapper{transform-origin:left top}.geometry-tools{gap:4px}.compact-button{font-size:10px;letter-spacing:.2px}}::ng-deep .mat-mdc-simple-snack-bar{background-color:#fff!important}\n"] }]
1216
+ }], ctorParameters: () => [], propDecorators: { map: [{
1217
+ type: Input,
1218
+ args: [{ required: true }]
1219
+ }], showMeasureDistance: [{
1220
+ type: Input
1221
+ }], showMeasureArea: [{
1222
+ type: Input
1223
+ }], collapsed: [{
1224
+ type: Input
1225
+ }], settings: [{
1226
+ type: Input,
1227
+ args: [{ required: true }]
1228
+ }], profile: [{
1229
+ type: Input,
1230
+ args: [{ required: true }]
1231
+ }], WKTInputEnabled: [{
1232
+ type: Input
1233
+ }], deleteEnabled: [{
1234
+ type: Input
1235
+ }] } });
1236
+
1237
+ class ActiveObjectsComponent {
1238
+ _drawLayerService = inject(DrawLayerSourceService);
1239
+ features$ = this._drawLayerService.features$;
1240
+ settings;
1241
+ activeFeatures = [];
1242
+ _featureHelper = inject(FeatureHelperService);
1243
+ constructor() {
1244
+ this.features$.subscribe({
1245
+ next: features => {
1246
+ const featureTypes = [...new Set(features.map(f => this._featureHelper.typeId(f)).filter(typeId => !!typeId))];
1247
+ const featuresObj = featureTypes
1248
+ .map(ft => ({ featureType: ft,
1249
+ display: this.settings.geometryTypeSettings.find(gts => gts.typeId === ft).typeName,
1250
+ features: features.filter(f => this._featureHelper.typeId(f) === ft)
1251
+ .map(f => ({ id: f.getId(), locked: this._featureHelper.isLocked(f), area: this._getAreaString(f) })) }));
1252
+ this.activeFeatures = [...featuresObj];
1253
+ }
1254
+ });
1255
+ }
1256
+ _getAreaString(feature) {
1257
+ if (feature.getGeometry()?.getType() !== 'Polygon') { // We only have polygon, linestring and points and only polygons have area
1258
+ return '';
1259
+ }
1260
+ return this.settings.sizeInHa ? `(${(getArea(feature.getGeometry()) / 10000).toFixed(2)} ha)` : `(${getArea(feature.getGeometry()).toFixed(2)} m2)`;
1261
+ }
1262
+ highlight(id) {
1263
+ console.log("🚀 ~ ActiveObjectsComponent ~ highlight ~ id:", id);
1264
+ }
1265
+ unhighlight(id) {
1266
+ console.log("🚀 ~ ActiveObjectsComponent ~ unhighlight ~ id:", id);
1267
+ }
1268
+ delete(id) {
1269
+ this._drawLayerService.remove(id);
1270
+ this.unhighlight(id);
1271
+ }
1272
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.15", ngImport: i0, type: ActiveObjectsComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1273
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.15", type: ActiveObjectsComponent, isStandalone: true, selector: "activeObjects", inputs: { settings: "settings" }, ngImport: i0, template: "<div class=\"active-objects-wrapper\" cdkDrag cdkDragBoundary=\".map-container\">\n <div class=\"drag-handle-active-objects\" cdkDragHandle>\n <mat-icon>drag_indicator</mat-icon>\n </div>\n <div class=\"active-objects-content\">\n <h4>Aktive flader</h4>\n @for(featureTypeObj of activeFeatures; track featureTypeObj.featureType) {\n <mat-expansion-panel>\n <mat-expansion-panel-header>\n <span class=\"panel-title\">\n {{featureTypeObj.display}} ({{featureTypeObj.features.length}})\n </span>\n </mat-expansion-panel-header>\n <div class=\"item-list\">\n @for(item of featureTypeObj.features; track item.id) {\n <div class=\"item\" (mouseenter)=\"highlight(item.id)\" (mouseleave)=\"unhighlight(item.id)\">\n <div class=\"item-text\">\n <span class=\"item-id\">{{item.id}}</span>\n <span class=\"item-area\">{{item.area}}</span>\n </div>\n <mat-icon *ngIf=\"!item.locked\" (click)=\"delete(item.id)\">delete</mat-icon>\n </div>\n }\n </div>\n </mat-expansion-panel>\n }\n </div> \n</div>", styles: ["@charset \"UTF-8\";.active-objects-wrapper{position:absolute;top:2em;left:5em;z-index:1000;cursor:grab;border-radius:8px;box-shadow:0 2px 10px #0000001a;background:color-mix(in srgb,#000 60%,transparent);max-width:295px;width:295px}.active-objects-wrapper.cdk-drag-dragging{opacity:.8;cursor:grab;z-index:1001}mat-expansion-panel ::ng-deep .mat-expansion-panel-header .panel-title{display:block;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:220px}.item{display:flex;justify-content:space-between;align-items:center;padding:8px 12px;border-radius:4px;background:color-mix(in srgb,#000 60%,transparent)!important;transition:all .2s ease;font-size:14px;color:#fff;min-height:40px;cursor:default}.item .item-text{flex:1;min-width:0;display:flex;flex-direction:column;overflow:hidden}.item .item-id{font-weight:500;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;margin-bottom:2px}.item .item-area{font-size:12px;color:#ccc;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.item mat-icon{cursor:pointer;color:#f44336;font-size:18px;width:18px;height:18px;flex-shrink:0;margin-left:8px}.item mat-icon:hover{color:#d32f2f}.drag-handle-active-objects{display:flex;align-items:center;justify-content:center;background:color-mix(in srgb,#000 0%,transparent);border-radius:8px 8px 0 0;padding:4px;cursor:grab}.drag-handle-active-objects mat-icon{color:#fff;font-size:18px;width:18px;height:18px}.active-objects-content{display:block;max-height:500px;overflow:auto}.active-objects-content::-webkit-scrollbar{width:8px;border-radius:10px;border:5px solid transparent}.active-objects-content::-webkit-scrollbar-thumb{background:#0000004d;border:5px solid transparent;border-radius:10px;background-clip:padding-box}.active-objects-content::-webkit-scrollbar-thumb:hover{background:#0006;background-clip:padding-box;border:3px solid transparent}h4{margin:0;padding:16px;background:color-mix(in srgb,#000 40%,transparent);font-weight:600;color:#fff;text-align:center;cursor:default}mat-expansion-panel{border-radius:0!important;box-shadow:none!important}mat-expansion-panel:last-child{border-bottom:none}mat-expansion-panel ::ng-deep .mat-expansion-panel-header{padding:0 16px;font-weight:500}mat-expansion-panel ::ng-deep .mat-expansion-panel-header .mat-content{display:flex;justify-content:space-between;align-items:center;color:#fff}mat-expansion-panel ::ng-deep .mat-expansion-panel-body{padding:0}.item-list{display:flex;flex-direction:column;gap:4px;padding:8px;max-height:300px;overflow-y:auto}.item-list::-webkit-scrollbar{width:6px}.item-list::-webkit-scrollbar-track{background:#f1f1f1;border-radius:3px}.item-list::-webkit-scrollbar-thumb{background:#c1c1c1;border-radius:3px}.item-list::-webkit-scrollbar-thumb:hover{background:#a8a8a8}@media (max-width: 768px){.active-objects-wrapper{right:.5em;top:8em;max-width:280px;width:280px}.item{font-size:13px;padding:6px 10px}h4{padding:12px;font-size:14px}}@media (max-width: 480px){.active-objects-wrapper{right:.5em;left:.5em;max-width:calc(100vw - 1em);width:auto}.item-list{max-height:200px}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "ngmodule", type: MatExpansionModule }, { kind: "component", type: i5.MatExpansionPanel, selector: "mat-expansion-panel", inputs: ["hideToggle", "togglePosition"], outputs: ["afterExpand", "afterCollapse"], exportAs: ["matExpansionPanel"] }, { kind: "component", type: i5.MatExpansionPanelHeader, selector: "mat-expansion-panel-header", inputs: ["expandedHeight", "collapsedHeight", "tabIndex"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i2.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: DragDropModule }, { kind: "directive", type: i4.CdkDrag, selector: "[cdkDrag]", inputs: ["cdkDragData", "cdkDragLockAxis", "cdkDragRootElement", "cdkDragBoundary", "cdkDragStartDelay", "cdkDragFreeDragPosition", "cdkDragDisabled", "cdkDragConstrainPosition", "cdkDragPreviewClass", "cdkDragPreviewContainer", "cdkDragScale"], outputs: ["cdkDragStarted", "cdkDragReleased", "cdkDragEnded", "cdkDragEntered", "cdkDragExited", "cdkDragDropped", "cdkDragMoved"], exportAs: ["cdkDrag"] }, { kind: "directive", type: i4.CdkDragHandle, selector: "[cdkDragHandle]", inputs: ["cdkDragHandleDisabled"] }] });
1274
+ }
1275
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.15", ngImport: i0, type: ActiveObjectsComponent, decorators: [{
1276
+ type: Component,
1277
+ args: [{ selector: 'activeObjects', imports: [CommonModule, MatExpansionModule, MatIconModule, DragDropModule], template: "<div class=\"active-objects-wrapper\" cdkDrag cdkDragBoundary=\".map-container\">\n <div class=\"drag-handle-active-objects\" cdkDragHandle>\n <mat-icon>drag_indicator</mat-icon>\n </div>\n <div class=\"active-objects-content\">\n <h4>Aktive flader</h4>\n @for(featureTypeObj of activeFeatures; track featureTypeObj.featureType) {\n <mat-expansion-panel>\n <mat-expansion-panel-header>\n <span class=\"panel-title\">\n {{featureTypeObj.display}} ({{featureTypeObj.features.length}})\n </span>\n </mat-expansion-panel-header>\n <div class=\"item-list\">\n @for(item of featureTypeObj.features; track item.id) {\n <div class=\"item\" (mouseenter)=\"highlight(item.id)\" (mouseleave)=\"unhighlight(item.id)\">\n <div class=\"item-text\">\n <span class=\"item-id\">{{item.id}}</span>\n <span class=\"item-area\">{{item.area}}</span>\n </div>\n <mat-icon *ngIf=\"!item.locked\" (click)=\"delete(item.id)\">delete</mat-icon>\n </div>\n }\n </div>\n </mat-expansion-panel>\n }\n </div> \n</div>", styles: ["@charset \"UTF-8\";.active-objects-wrapper{position:absolute;top:2em;left:5em;z-index:1000;cursor:grab;border-radius:8px;box-shadow:0 2px 10px #0000001a;background:color-mix(in srgb,#000 60%,transparent);max-width:295px;width:295px}.active-objects-wrapper.cdk-drag-dragging{opacity:.8;cursor:grab;z-index:1001}mat-expansion-panel ::ng-deep .mat-expansion-panel-header .panel-title{display:block;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:220px}.item{display:flex;justify-content:space-between;align-items:center;padding:8px 12px;border-radius:4px;background:color-mix(in srgb,#000 60%,transparent)!important;transition:all .2s ease;font-size:14px;color:#fff;min-height:40px;cursor:default}.item .item-text{flex:1;min-width:0;display:flex;flex-direction:column;overflow:hidden}.item .item-id{font-weight:500;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;margin-bottom:2px}.item .item-area{font-size:12px;color:#ccc;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.item mat-icon{cursor:pointer;color:#f44336;font-size:18px;width:18px;height:18px;flex-shrink:0;margin-left:8px}.item mat-icon:hover{color:#d32f2f}.drag-handle-active-objects{display:flex;align-items:center;justify-content:center;background:color-mix(in srgb,#000 0%,transparent);border-radius:8px 8px 0 0;padding:4px;cursor:grab}.drag-handle-active-objects mat-icon{color:#fff;font-size:18px;width:18px;height:18px}.active-objects-content{display:block;max-height:500px;overflow:auto}.active-objects-content::-webkit-scrollbar{width:8px;border-radius:10px;border:5px solid transparent}.active-objects-content::-webkit-scrollbar-thumb{background:#0000004d;border:5px solid transparent;border-radius:10px;background-clip:padding-box}.active-objects-content::-webkit-scrollbar-thumb:hover{background:#0006;background-clip:padding-box;border:3px solid transparent}h4{margin:0;padding:16px;background:color-mix(in srgb,#000 40%,transparent);font-weight:600;color:#fff;text-align:center;cursor:default}mat-expansion-panel{border-radius:0!important;box-shadow:none!important}mat-expansion-panel:last-child{border-bottom:none}mat-expansion-panel ::ng-deep .mat-expansion-panel-header{padding:0 16px;font-weight:500}mat-expansion-panel ::ng-deep .mat-expansion-panel-header .mat-content{display:flex;justify-content:space-between;align-items:center;color:#fff}mat-expansion-panel ::ng-deep .mat-expansion-panel-body{padding:0}.item-list{display:flex;flex-direction:column;gap:4px;padding:8px;max-height:300px;overflow-y:auto}.item-list::-webkit-scrollbar{width:6px}.item-list::-webkit-scrollbar-track{background:#f1f1f1;border-radius:3px}.item-list::-webkit-scrollbar-thumb{background:#c1c1c1;border-radius:3px}.item-list::-webkit-scrollbar-thumb:hover{background:#a8a8a8}@media (max-width: 768px){.active-objects-wrapper{right:.5em;top:8em;max-width:280px;width:280px}.item{font-size:13px;padding:6px 10px}h4{padding:12px;font-size:14px}}@media (max-width: 480px){.active-objects-wrapper{right:.5em;left:.5em;max-width:calc(100vw - 1em);width:auto}.item-list{max-height:200px}}\n"] }]
1278
+ }], ctorParameters: () => [], propDecorators: { settings: [{
1279
+ type: Input,
1280
+ args: [{ required: true }]
1281
+ }] } });
1282
+
1283
+ class MapSearchComponent {
1284
+ searchAddress = '';
1285
+ allResults = ['address1', 'address2', 'address3', 'address4'];
1286
+ filteredResults = [];
1287
+ ngOnInit() {
1288
+ this.filterResults();
1289
+ }
1290
+ filterResults() {
1291
+ if (!this.searchAddress) {
1292
+ this.filteredResults = [];
1293
+ return;
1294
+ }
1295
+ this.filteredResults = this.allResults.filter(r => r.toLowerCase().includes(this.searchAddress.toLowerCase()));
1296
+ }
1297
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.15", ngImport: i0, type: MapSearchComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1298
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.15", type: MapSearchComponent, isStandalone: true, selector: "lib-map-search", ngImport: i0, template: "<div class=\"search-container\" cdkDrag cdkDragBoundary=\".map-container\">\n <div class=\"drag-handle\" cdkDragHandle>\n <mat-icon>drag_indicator</mat-icon>\n </div>\n <mat-form-field appearance=\"outline\" class=\"search-field\">\n <mat-icon class=\"search-icon\" matPrefix>search</mat-icon>\n <mat-label>S\u00F8ge</mat-label>\n <input\n type=\"text\"\n matInput\n [(ngModel)]=\"searchAddress\"\n [matAutocomplete]=\"auto\"\n (input)=\"filterResults()\"\n />\n <mat-autocomplete #auto=\"matAutocomplete\">\n <mat-option *ngFor=\"let result of filteredResults\" [value]=\"result\">\n {{ result }}\n </mat-option>\n </mat-autocomplete>\n </mat-form-field>\n</div>", styles: [".search-container{position:absolute;top:2em;left:25em;z-index:1000;cursor:grab}.search-container.cdk-drag-dragging{opacity:.8;cursor:grab}.drag-handle{display:flex;align-items:center;justify-content:center;background:color-mix(in srgb,#000 60%,transparent);padding:4px;cursor:grab;border-radius:8px 8px 0 0}.drag-handle mat-icon{color:#fff;font-size:20px;width:20px;height:20px}::ng-deep .mdc-text-field--outlined:not(.mdc-text-field--disabled) .mdc-text-field__input{color:#fff!important}::ng-deep .search-icon{color:#fff!important;font-size:20px;width:20px;height:20px;transition:color .3s ease}::ng-deep .search-icon:hover .search-icon{color:#fff!important}::ng-deep .search-icon.mat-focused ::ng-deep .search-icon-button .search-icon{color:#fff!important}.search-field{width:290px;cursor:pointer}.search-field ::ng-deep .mat-mdc-form-field-focus-overlay{background-color:transparent}.search-field ::ng-deep .mdc-notched-outline .mdc-notched-outline__leading,.search-field ::ng-deep .mdc-notched-outline .mdc-notched-outline__notch,.search-field ::ng-deep .mdc-notched-outline .mdc-notched-outline__trailing{border-color:transparent!important;border-width:1.5px!important;transition:border-color .3s ease,border-width .3s ease}.search-field:not(.mat-focused):hover ::ng-deep .mdc-notched-outline .mdc-notched-outline__leading,.search-field:not(.mat-focused):hover ::ng-deep .mdc-notched-outline .mdc-notched-outline__notch,.search-field:not(.mat-focused):hover ::ng-deep .mdc-notched-outline .mdc-notched-outline__trailing{border-color:transparent!important}.search-field ::ng-deep .mat-mdc-form-field-label{color:#666!important;font-weight:500;font-size:15px}.search-field ::ng-deep .mat-mdc-form-field.mat-focused .mat-mdc-form-field-label{color:#2196f3!important}.search-field ::ng-deep .mat-mdc-input-element{font-size:15px;color:#333;font-weight:400;line-height:1.5}.search-field ::ng-deep .mat-mdc-input-element::placeholder{color:#999;font-weight:400;opacity:.8}.search-field ::ng-deep .mat-icon.search-icon{color:#fff;margin-left:12px;margin-right:-8px;transition:color .3s ease}.search-field.mat-focused ::ng-deep .mat-icon.search-icon{color:#2196f3}.search-field ::ng-deep .mdc-floating-label{padding:0 4px;left:40px!important}::ng-deep .mat-mdc-form-field.mat-form-field-animations-enabled .mdc-text-field__input{margin-left:12px}::ng-deep .mat-mdc-autocomplete-panel .mat-mdc-option .mdc-list-item__primary-text{color:#fff!important}::ng-deep .mat-mdc-option .mat-pseudo-checkbox-minimal{color:#fff!important}::ng-deep .mat-mdc-autocomplete-panel{border-radius:12px!important;max-height:320px!important;background:color-mix(in srgb,#000 60%,transparent)!important;box-shadow:0 8px 24px #00000026!important;margin-top:8px!important;overflow:hidden!important}::ng-deep .mat-mdc-autocomplete-panel .mat-mdc-option{min-height:52px!important;margin:4px 8px!important;border-radius:8px!important;transition:all .2s cubic-bezier(.4,0,.2,1);position:relative;overflow:hidden}::ng-deep .mat-mdc-autocomplete-panel .mat-mdc-option:hover:not(.mdc-list-item--disabled){background-color:#0000004d!important}::ng-deep .mat-mdc-autocomplete-panel .mat-mdc-option .mdc-list-item__primary-text{font-size:14px;font-weight:400;color:inherit;line-height:1.5}::ng-deep .mat-mdc-autocomplete-panel::-webkit-scrollbar{width:6px}::ng-deep .mat-mdc-autocomplete-panel::-webkit-scrollbar-track{background:#f1f1f1;border-radius:0 12px 12px 0}::ng-deep .mat-mdc-autocomplete-panel::-webkit-scrollbar-thumb{background:#c1c1c1;border-radius:3px;transition:background .3s ease}::ng-deep .mat-mdc-autocomplete-panel::-webkit-scrollbar-thumb:hover{background:#a8a8a8}::ng-deep .mat-mdc-text-field-wrapper{background:color-mix(in srgb,#000 60%,transparent);border-radius:0 0 12px 12px;box-shadow:#0000004d 0 1px 4px -1px;transition:all .3s cubic-bezier(.4,0,.2,1)}::ng-deep .mat-mdc-text-field-wrapper:hover{box-shadow:0 4px 16px #00000026}::ng-deep .mat-mdc-form-field:not(.mat-form-field-disabled) .mat-mdc-floating-label.mdc-floating-label{color:#fff!important}@media (max-width: 768px){.search-container{left:2em;top:1.5em}.search-field{width:320px}}@media (max-width: 480px){.search-container{left:1.5em;right:1.5em}.search-field{width:calc(100vw - 3em)}.search-field ::ng-deep .mat-mdc-input-element{font-size:14px}::ng-deep .mat-mdc-autocomplete-panel{max-height:240px!important}::ng-deep .mat-mdc-autocomplete-panel .mat-mdc-option{min-height:48px!important;padding:10px 16px!important}}::ng-deep .mdc-text-field{border-top-left-radius:0!important;border-top-right-radius:0!important}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i3$1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i3$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i3$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: MatFormFieldModule }, { kind: "component", type: i3.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i3.MatLabel, selector: "mat-label" }, { kind: "directive", type: i3.MatPrefix, selector: "[matPrefix], [matIconPrefix], [matTextPrefix]", inputs: ["matTextPrefix"] }, { kind: "ngmodule", type: MatInputModule }, { kind: "directive", type: i6.MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly", "disabledInteractive"], exportAs: ["matInput"] }, { kind: "ngmodule", type: MatAutocompleteModule }, { kind: "component", type: i5$1.MatAutocomplete, selector: "mat-autocomplete", inputs: ["aria-label", "aria-labelledby", "displayWith", "autoActiveFirstOption", "autoSelectActiveOption", "requireSelection", "panelWidth", "disableRipple", "class", "hideSingleSelectionIndicator"], outputs: ["optionSelected", "opened", "closed", "optionActivated"], exportAs: ["matAutocomplete"] }, { kind: "component", type: i3$2.MatOption, selector: "mat-option", inputs: ["value", "id", "disabled"], outputs: ["onSelectionChange"], exportAs: ["matOption"] }, { kind: "directive", type: i5$1.MatAutocompleteTrigger, selector: "input[matAutocomplete], textarea[matAutocomplete]", inputs: ["matAutocomplete", "matAutocompletePosition", "matAutocompleteConnectedTo", "autocomplete", "matAutocompleteDisabled"], exportAs: ["matAutocompleteTrigger"] }, { kind: "ngmodule", type: MatOptionModule }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i2.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: DragDropModule }, { kind: "directive", type: i4.CdkDrag, selector: "[cdkDrag]", inputs: ["cdkDragData", "cdkDragLockAxis", "cdkDragRootElement", "cdkDragBoundary", "cdkDragStartDelay", "cdkDragFreeDragPosition", "cdkDragDisabled", "cdkDragConstrainPosition", "cdkDragPreviewClass", "cdkDragPreviewContainer", "cdkDragScale"], outputs: ["cdkDragStarted", "cdkDragReleased", "cdkDragEnded", "cdkDragEntered", "cdkDragExited", "cdkDragDropped", "cdkDragMoved"], exportAs: ["cdkDrag"] }, { kind: "directive", type: i4.CdkDragHandle, selector: "[cdkDragHandle]", inputs: ["cdkDragHandleDisabled"] }] });
1299
+ }
1300
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.15", ngImport: i0, type: MapSearchComponent, decorators: [{
1301
+ type: Component,
1302
+ args: [{ selector: 'lib-map-search', imports: [
1303
+ CommonModule,
1304
+ FormsModule,
1305
+ MatFormFieldModule,
1306
+ MatInputModule,
1307
+ MatAutocompleteModule,
1308
+ MatOptionModule,
1309
+ MatIconModule,
1310
+ DragDropModule
1311
+ ], template: "<div class=\"search-container\" cdkDrag cdkDragBoundary=\".map-container\">\n <div class=\"drag-handle\" cdkDragHandle>\n <mat-icon>drag_indicator</mat-icon>\n </div>\n <mat-form-field appearance=\"outline\" class=\"search-field\">\n <mat-icon class=\"search-icon\" matPrefix>search</mat-icon>\n <mat-label>S\u00F8ge</mat-label>\n <input\n type=\"text\"\n matInput\n [(ngModel)]=\"searchAddress\"\n [matAutocomplete]=\"auto\"\n (input)=\"filterResults()\"\n />\n <mat-autocomplete #auto=\"matAutocomplete\">\n <mat-option *ngFor=\"let result of filteredResults\" [value]=\"result\">\n {{ result }}\n </mat-option>\n </mat-autocomplete>\n </mat-form-field>\n</div>", styles: [".search-container{position:absolute;top:2em;left:25em;z-index:1000;cursor:grab}.search-container.cdk-drag-dragging{opacity:.8;cursor:grab}.drag-handle{display:flex;align-items:center;justify-content:center;background:color-mix(in srgb,#000 60%,transparent);padding:4px;cursor:grab;border-radius:8px 8px 0 0}.drag-handle mat-icon{color:#fff;font-size:20px;width:20px;height:20px}::ng-deep .mdc-text-field--outlined:not(.mdc-text-field--disabled) .mdc-text-field__input{color:#fff!important}::ng-deep .search-icon{color:#fff!important;font-size:20px;width:20px;height:20px;transition:color .3s ease}::ng-deep .search-icon:hover .search-icon{color:#fff!important}::ng-deep .search-icon.mat-focused ::ng-deep .search-icon-button .search-icon{color:#fff!important}.search-field{width:290px;cursor:pointer}.search-field ::ng-deep .mat-mdc-form-field-focus-overlay{background-color:transparent}.search-field ::ng-deep .mdc-notched-outline .mdc-notched-outline__leading,.search-field ::ng-deep .mdc-notched-outline .mdc-notched-outline__notch,.search-field ::ng-deep .mdc-notched-outline .mdc-notched-outline__trailing{border-color:transparent!important;border-width:1.5px!important;transition:border-color .3s ease,border-width .3s ease}.search-field:not(.mat-focused):hover ::ng-deep .mdc-notched-outline .mdc-notched-outline__leading,.search-field:not(.mat-focused):hover ::ng-deep .mdc-notched-outline .mdc-notched-outline__notch,.search-field:not(.mat-focused):hover ::ng-deep .mdc-notched-outline .mdc-notched-outline__trailing{border-color:transparent!important}.search-field ::ng-deep .mat-mdc-form-field-label{color:#666!important;font-weight:500;font-size:15px}.search-field ::ng-deep .mat-mdc-form-field.mat-focused .mat-mdc-form-field-label{color:#2196f3!important}.search-field ::ng-deep .mat-mdc-input-element{font-size:15px;color:#333;font-weight:400;line-height:1.5}.search-field ::ng-deep .mat-mdc-input-element::placeholder{color:#999;font-weight:400;opacity:.8}.search-field ::ng-deep .mat-icon.search-icon{color:#fff;margin-left:12px;margin-right:-8px;transition:color .3s ease}.search-field.mat-focused ::ng-deep .mat-icon.search-icon{color:#2196f3}.search-field ::ng-deep .mdc-floating-label{padding:0 4px;left:40px!important}::ng-deep .mat-mdc-form-field.mat-form-field-animations-enabled .mdc-text-field__input{margin-left:12px}::ng-deep .mat-mdc-autocomplete-panel .mat-mdc-option .mdc-list-item__primary-text{color:#fff!important}::ng-deep .mat-mdc-option .mat-pseudo-checkbox-minimal{color:#fff!important}::ng-deep .mat-mdc-autocomplete-panel{border-radius:12px!important;max-height:320px!important;background:color-mix(in srgb,#000 60%,transparent)!important;box-shadow:0 8px 24px #00000026!important;margin-top:8px!important;overflow:hidden!important}::ng-deep .mat-mdc-autocomplete-panel .mat-mdc-option{min-height:52px!important;margin:4px 8px!important;border-radius:8px!important;transition:all .2s cubic-bezier(.4,0,.2,1);position:relative;overflow:hidden}::ng-deep .mat-mdc-autocomplete-panel .mat-mdc-option:hover:not(.mdc-list-item--disabled){background-color:#0000004d!important}::ng-deep .mat-mdc-autocomplete-panel .mat-mdc-option .mdc-list-item__primary-text{font-size:14px;font-weight:400;color:inherit;line-height:1.5}::ng-deep .mat-mdc-autocomplete-panel::-webkit-scrollbar{width:6px}::ng-deep .mat-mdc-autocomplete-panel::-webkit-scrollbar-track{background:#f1f1f1;border-radius:0 12px 12px 0}::ng-deep .mat-mdc-autocomplete-panel::-webkit-scrollbar-thumb{background:#c1c1c1;border-radius:3px;transition:background .3s ease}::ng-deep .mat-mdc-autocomplete-panel::-webkit-scrollbar-thumb:hover{background:#a8a8a8}::ng-deep .mat-mdc-text-field-wrapper{background:color-mix(in srgb,#000 60%,transparent);border-radius:0 0 12px 12px;box-shadow:#0000004d 0 1px 4px -1px;transition:all .3s cubic-bezier(.4,0,.2,1)}::ng-deep .mat-mdc-text-field-wrapper:hover{box-shadow:0 4px 16px #00000026}::ng-deep .mat-mdc-form-field:not(.mat-form-field-disabled) .mat-mdc-floating-label.mdc-floating-label{color:#fff!important}@media (max-width: 768px){.search-container{left:2em;top:1.5em}.search-field{width:320px}}@media (max-width: 480px){.search-container{left:1.5em;right:1.5em}.search-field{width:calc(100vw - 3em)}.search-field ::ng-deep .mat-mdc-input-element{font-size:14px}::ng-deep .mat-mdc-autocomplete-panel{max-height:240px!important}::ng-deep .mat-mdc-autocomplete-panel .mat-mdc-option{min-height:48px!important;padding:10px 16px!important}}::ng-deep .mdc-text-field{border-top-left-radius:0!important;border-top-right-radius:0!important}\n"] }]
1312
+ }] });
1313
+
335
1314
  class GisKomponentComponent {
336
1315
  _profileService = inject(ProfileService);
1316
+ _undoRedo = inject(UndoRedoService);
337
1317
  _http = inject(HttpClient);
338
1318
  _layerHelper = inject(LayerHelperService);
1319
+ _layerErrorService = inject(LayerErrorService);
339
1320
  identifier;
340
1321
  settings;
1322
+ toolbarRef;
1323
+ activeObjectsRef;
341
1324
  map;
342
1325
  toolbarCollapsed = false;
343
1326
  layerPanelCollapsed = false;
@@ -345,19 +1328,19 @@ class GisKomponentComponent {
345
1328
  conflictsCollapsed = false;
346
1329
  showConflicts = false;
347
1330
  showObjects = false;
1331
+ showActiveObjects = false;
348
1332
  profiles = [];
349
1333
  selectedProfile;
1334
+ showToolbar = false;
350
1335
  _komponentSettingsHelper = inject(KomponentSettingsHelperService);
351
1336
  layers = [];
352
- // activeObjects = [
353
- // { name: 'Objekt A', size: '22 m2' },
354
- // { name: 'Objekt B', size: '108,4 m2' }
355
- // ];
356
1337
  source = new VectorSource({ wrapX: false });
357
- layer = new ol_layer_Vector({ source: this.source });
1338
+ layer = new VectorLayer({ source: this.source });
358
1339
  draw = new Draw({ source: this.source, type: 'Polygon' });
359
1340
  move = new Translate({ layers: [this.layer] });
360
1341
  mousePositionCtrl = new ol_control_mouse({ projection: 'EPSG:25832' });
1342
+ onMoveEndCheckZoomFunction = () => { };
1343
+ currentZoomLevel = 0;
361
1344
  scaleCtrl = new ol_control_scale({
362
1345
  units: 'metric',
363
1346
  bar: true,
@@ -377,29 +1360,48 @@ class GisKomponentComponent {
377
1360
  minZoom: profile.minZoom ?? undefined,
378
1361
  projection: 'EPSG:25832'
379
1362
  });
1363
+ this.currentZoomLevel = profile.zoom ?? 8;
380
1364
  this._getCapabilitiesObject(profile).subscribe({
381
1365
  next: capabilityObject => {
382
1366
  const layers = [...profile.layerGroups.map((lg) => new OLLayerGroup({
383
1367
  layers: lg.layers
384
1368
  .filter(layer => layer.activeInSelector)
385
- .map(l => this._mapLayer(l, capabilityObject, 'EPSG:25832', lg.id)) // for now, we default to EPSG:25832.
1369
+ .sort((a, b) => a.sortOrder - b.sortOrder)
1370
+ .map((l, index) => this._mapLayer(l, capabilityObject, 'EPSG:25832', lg.id, index)) // for now, we default to EPSG:25832.
386
1371
  }))
387
1372
  ];
388
1373
  this.map.setLayers(layers);
389
- if (this.settings && profile.styleRepositoryWorkspace && profile.styleRepositoryGeoserver) {
390
- this._komponentSettingsHelper.loadThem(this.settings, profile.styleRepositoryWorkspace, profile.styleRepositoryGeoserver, this.map);
391
- }
392
- this._layerHelper.applyCachedLayersToDisplayInMap(this.map);
1374
+ this._layerHelper.applyCachedLayersToDisplayInMap(this.map, profile.id);
393
1375
  }
394
1376
  });
1377
+ const toolbarControl = new Control({
1378
+ element: this.toolbarRef.nativeElement
1379
+ });
1380
+ if (profile.showToolbar) {
1381
+ this.showToolbar = true;
1382
+ this.map.addControl(toolbarControl);
1383
+ }
1384
+ this.showActiveObjects = true;
1385
+ const activeObjectsControl = new Control({
1386
+ element: this.activeObjectsRef.nativeElement
1387
+ });
1388
+ this.map.addControl(activeObjectsControl);
395
1389
  this._handleShowMouseCoordinates(profile.showCoordinates);
396
1390
  this._handleShowScale(profile.showScale);
397
1391
  this._setLogo(profile.logoUrl);
398
1392
  this._setCopyRight(profile.copyrightMessage);
399
1393
  this.map.setView(view);
1394
+ this.onMoveEndCheckZoomFunction = () => {
1395
+ this._checkZoom(this.map);
1396
+ };
1397
+ this.map.on('moveend', this.onMoveEndCheckZoomFunction);
1398
+ this._undoRedo.init();
400
1399
  }
401
1400
  });
402
1401
  }
1402
+ _checkZoom(map) {
1403
+ this.currentZoomLevel = map.getView().getZoom() ?? this.currentZoomLevel;
1404
+ }
403
1405
  _getCapabilitiesObject(profile) {
404
1406
  // Find all WMTS-layers and get their base url's for retrieving the capabilities. Make sure every url is only called once by removing duplicates
405
1407
  const getCapabilitiesUrls = [...new Set(profile.layerGroups.flatMap(lg => lg.layers).filter(lg => lg.layerType === 'WMTS').map(lg => lg.baseUrl))];
@@ -415,7 +1417,7 @@ class GisKomponentComponent {
415
1417
  return obj;
416
1418
  }, {})));
417
1419
  }
418
- _mapLayer(layer, wmtsOptions, projection, layerGroupDbId) {
1420
+ _mapLayer(layer, wmtsOptions, projection, layerGroupDbId, zIndex) {
419
1421
  const params = {
420
1422
  layers: layer.layers,
421
1423
  };
@@ -432,7 +1434,7 @@ class GisKomponentComponent {
432
1434
  source: new ImageWMS({
433
1435
  url: layer.baseUrl,
434
1436
  params
435
- }),
1437
+ })
436
1438
  });
437
1439
  break;
438
1440
  case 'TILEWMS':
@@ -465,6 +1467,8 @@ class GisKomponentComponent {
465
1467
  }
466
1468
  this._layerHelper.setDbId(result, layer.id);
467
1469
  this._layerHelper.setLayerGroupDbId(result, layerGroupDbId);
1470
+ this._layerErrorService.registerLayer(layer.id, result);
1471
+ result.setZIndex(zIndex);
468
1472
  return result;
469
1473
  }
470
1474
  _setLogo(url) {
@@ -493,10 +1497,9 @@ class GisKomponentComponent {
493
1497
  }
494
1498
  ngOnInit() {
495
1499
  this.profileSelected(this.identifier);
496
- proj4.defs("EPSG:25832", "+proj=utm +zone=32 +ellps=GRS80 +units=m +no_defs +type=crs");
1500
+ proj4.defs('EPSG:25832', '+proj=utm +zone=32 +ellps=GRS80 +units=m +no_defs +type=crs');
497
1501
  register(proj4);
498
- // const projection = getProjection("EPSG:25832");
499
- this.map = new Map({
1502
+ this.map = new Map$1({
500
1503
  target: 'map',
501
1504
  });
502
1505
  this.draw.setActive(false);
@@ -528,22 +1531,43 @@ class GisKomponentComponent {
528
1531
  this.activeObjectsCollapsed = !this.activeObjectsCollapsed;
529
1532
  }
530
1533
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.15", ngImport: i0, type: GisKomponentComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
531
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.15", type: GisKomponentComponent, isStandalone: true, selector: "gis-komponent", inputs: { identifier: "identifier", settings: "settings" }, providers: [LayerHelperService], ngImport: i0, template: " <lib-layer-selector [map]=\"map\" [profile]=\"selectedProfile\"></lib-layer-selector>\n\n<div class=\"map-container\">\n\n <!-- Objektvisning -->\n <!-- @if (showObjects) {\n <div class=\"object-panel\" [class.collapsed]=\"activeObjectsCollapsed\">\n <div class=\"header\" (click)=\"toggleActiveObjectsCollapsed()\">\n <span>Aktive objekter ({{activeObjects.length}})</span>\n <mat-icon>{{ activeObjectsCollapsed ? 'expand_more' : 'expand_less' }}</mat-icon>\n </div>\n @if (!activeObjectsCollapsed){\n <ul>\n <li *ngFor=\"let obj of activeObjects\">{{ obj.name }} ({{obj.size}})</li>\n </ul>\n }\n </div>\n } -->\n\n <!-- Konflikter -->\n @if (showConflicts) {\n\n <div class=\"conflict-panel\" [class.collapsed]=\"conflictsCollapsed\">\n <div class=\"header\">\n <span>Konflikter (3)</span>\n <mat-icon>{{ activeObjectsCollapsed ? 'expand_more' : 'expand_less' }}</mat-icon> -->\n </div>\n <mat-list>\n <mat-list-item>\n <span matListItemTitle>Lag #1</span>\n <span matListItemLine><mat-icon>highlight</mat-icon> Objekt #1</span>\n </mat-list-item>\n <mat-list-item>\n <span matListItemTitle>Lag #2 (svarer ikke)</span>\n </mat-list-item>\n <mat-list-item>\n <span matListItemTitle>Lag #3</span>\n <span matListItemLine><mat-icon>highlight</mat-icon> Objekt #1</span>\n <span matListItemLine><mat-icon>highlight</mat-icon> Objekt #2</span>\n </mat-list-item>\n </mat-list>\n </div>\n \n } \n\n <!-- Kort -->\n <div id=\"map\" class=\"map\"></div>\n</div>\n", styles: ["::ng-deep .map-container{position:relative;height:85vh;width:100%}::ng-deep #map{width:100%;height:100%;position:absolute;inset:0}::ng-deep ::ng-deep .ol-logo{position:absolute;left:auto;right:3em;top:6.25em}::ng-deep ::ng-deep .ol-copyright{position:absolute;left:auto;right:calc(50vw - 90px);bottom:.5em}::ng-deep ::ng-deep .toolbar{position:absolute;top:10px;left:10px;background:#fff;padding:4px;border-radius:4px;display:flex;flex-direction:column;transition:width .3s;z-index:1000}::ng-deep ::ng-deep .toolbar.collapsed{width:40px;overflow:hidden}::ng-deep .object-panel{position:absolute;bottom:10px;left:10px;background:#fff;padding:8px;border-radius:4px;z-index:1000}::ng-deep .object-panel .header{display:flex;justify-content:space-between;padding:8px;cursor:pointer;background:#f0f0f0}::ng-deep .conflict-panel{position:absolute;bottom:10px;right:10px;background:#fff;padding:8px;border-radius:4px;z-index:1000}::ng-deep .conflict-panel .header{display:flex;justify-content:space-between;padding:8px;cursor:pointer;background:#f0f0f0}::ng-deep ::ng-deep .ol-zoom.ol-unselectable.ol-control{display:flex;flex-direction:column;position:absolute;top:2rem;left:1rem}::ng-deep ::ng-deep button.ol-zoom-in{font-size:28px;width:35px;height:35px;background-color:#fff;border:1px solid rgba(255,255,255,.8);box-shadow:#0000004d 0 1px 4px -1px;cursor:pointer;color:#334155}::ng-deep button.ol-zoom-out{font-size:28px;width:35px;height:35px;background-color:#fff;border:1px solid rgba(255,255,255,.8);box-shadow:#0000004d 0 1px 4px -1px;cursor:pointer;color:#334155}::ng-deep button.ol-rotate-reset{font-size:28px;width:35px;height:35px;background-color:#fff;border:1px solid rgba(255,255,255,.8);box-shadow:#0000004d 0 1px 4px -1px;cursor:pointer;color:#334155;margin-top:10px}::ng-deep .ol-scale-bar.ol-unselectable{position:absolute;bottom:1rem;left:1rem}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i2.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatListModule }, { kind: "component", type: i2$1.MatList, selector: "mat-list", exportAs: ["matList"] }, { kind: "component", type: i2$1.MatListItem, selector: "mat-list-item, a[mat-list-item], button[mat-list-item]", inputs: ["activated"], exportAs: ["matListItem"] }, { kind: "directive", type: i2$1.MatListItemLine, selector: "[matListItemLine]" }, { kind: "directive", type: i2$1.MatListItemTitle, selector: "[matListItemTitle]" }, { kind: "ngmodule", type: MatSelectModule }, { kind: "component", type: LayerSelectorComponent, selector: "lib-layer-selector", inputs: ["map", "profile"] }] });
1534
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.15", type: GisKomponentComponent, isStandalone: true, selector: "gis-komponent", inputs: { identifier: "identifier", settings: "settings" }, providers: [LayerHelperService], viewQueries: [{ propertyName: "toolbarRef", first: true, predicate: ["toolbarRef"], descendants: true, static: true }, { propertyName: "activeObjectsRef", first: true, predicate: ["activeObjectsRef"], descendants: true, static: true }], ngImport: i0, template: "<lib-layer-selector [map]=\"map\" [profile]=\"selectedProfile\" [currentZoomLevel]=\"currentZoomLevel\"></lib-layer-selector>\n<span>Zoom: {{ currentZoomLevel}}</span>\n<div #activeObjectsRef>\n @if(showActiveObjects && settings) {\n <activeObjects [settings]=\"settings\"></activeObjects>\n }\n</div>\n<div #toolbarRef class=\"map-toolbar-container\">\n @if(showToolbar && settings) {\n <map-toolbox [map]=\"map\" \n [profile]=\"selectedProfile\"\n [settings]=\"settings\"\n [collapsed]=\"selectedProfile.toolbarCollapsed\" [WKTInputEnabled]=\"settings?.WKTInputEnabled\" [deleteEnabled]=\"settings?.deleteEnabled\" [showMeasureArea]=\"selectedProfile.showAreaMeasurement\" [showMeasureDistance]=\"selectedProfile.showDistanceMeasurement\"></map-toolbox>\n }\n</div>\n\n<div class=\"map-container\">\n <lib-map-search></lib-map-search>\n\n <!-- Objektvisning -->\n <!-- @if (showObjects) {\n <div class=\"object-panel\" [class.collapsed]=\"activeObjectsCollapsed\">\n <div class=\"header\" (click)=\"toggleActiveObjectsCollapsed()\">\n <span>Aktive objekter ({{activeObjects.length}})</span>\n <mat-icon>{{ activeObjectsCollapsed ? 'expand_more' : 'expand_less' }}</mat-icon>\n </div>\n @if (!activeObjectsCollapsed){\n <ul>\n <li *ngFor=\"let obj of activeObjects\">{{ obj.name }} ({{obj.size}})</li>\n </ul>\n }\n </div>\n } -->\n\n <!-- Konflikter -->\n @if (showConflicts) {\n\n <div class=\"conflict-panel\" [class.collapsed]=\"conflictsCollapsed\">\n <div class=\"header\">\n <span>Konflikter (3)</span>\n <mat-icon>{{ activeObjectsCollapsed ? 'expand_more' : 'expand_less' }}</mat-icon> -->\n </div>\n <mat-list>\n <mat-list-item>\n <span matListItemTitle>Lag #1</span>\n <span matListItemLine><mat-icon>highlight</mat-icon> Objekt #1</span>\n </mat-list-item>\n <mat-list-item>\n <span matListItemTitle>Lag #2 (svarer ikke)</span>\n </mat-list-item>\n <mat-list-item>\n <span matListItemTitle>Lag #3</span>\n <span matListItemLine><mat-icon>highlight</mat-icon> Objekt #1</span>\n <span matListItemLine><mat-icon>highlight</mat-icon> Objekt #2</span>\n </mat-list-item>\n </mat-list>\n </div>\n \n } \n\n <!-- Kort -->\n <div id=\"map\" class=\"map\"></div>\n \n\n</div>\n", styles: ["::ng-deep .lib-error-snackbar{background-color:#d32f2f;color:#fff;font-weight:500}::ng-deep .map-container{position:relative;height:85vh;width:100%}::ng-deep #map{width:100%;height:100%;position:absolute;inset:0}::ng-deep ::ng-deep .ol-logo{position:absolute;left:auto;right:3em;top:6.25em}::ng-deep ::ng-deep .ol-copyright{position:absolute;left:auto;right:calc(50vw - 90px);bottom:.5em}::ng-deep ::ng-deep .toolbar{position:absolute;top:10px;left:10px;background:#fff;padding:4px;border-radius:4px;display:flex;flex-direction:column;transition:width .3s;z-index:1000}::ng-deep ::ng-deep .toolbar.collapsed{width:40px;overflow:hidden}::ng-deep .object-panel{position:absolute;bottom:10px;left:10px;background:#fff;padding:8px;border-radius:4px;z-index:1000}::ng-deep .object-panel .header{display:flex;justify-content:space-between;padding:8px;cursor:pointer;background:#f0f0f0}::ng-deep .conflict-panel{position:absolute;bottom:10px;right:10px;background:#fff;padding:8px;border-radius:4px;z-index:1000}::ng-deep .conflict-panel .header{display:flex;justify-content:space-between;padding:8px;cursor:pointer;background:#f0f0f0}::ng-deep ::ng-deep .ol-zoom.ol-unselectable.ol-control{display:flex;flex-direction:column;position:absolute;top:2rem;left:1rem}::ng-deep ::ng-deep button.ol-zoom-in{font-size:2em;width:40px;height:40px;background-color:#fff;border:1px solid rgba(255,255,255,.8);box-shadow:#0000004d 0 1px 4px -1px;cursor:pointer;color:#666}::ng-deep button.ol-zoom-out{font-size:2em;width:40px;height:40px;background-color:#fff;border:1px solid rgba(255,255,255,.8);box-shadow:#0000004d 0 1px 4px -1px;cursor:pointer;color:#666}::ng-deep button.ol-rotate-reset{font-size:28px;width:35px;height:35px;background-color:#fff;border:1px solid rgba(255,255,255,.8);box-shadow:#0000004d 0 1px 4px -1px;cursor:pointer;color:#334155;margin-top:10px}::ng-deep .ol-scale-bar.ol-unselectable{position:absolute;bottom:1rem;left:1rem}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i2.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatListModule }, { kind: "component", type: i2$1.MatList, selector: "mat-list", exportAs: ["matList"] }, { kind: "component", type: i2$1.MatListItem, selector: "mat-list-item, a[mat-list-item], button[mat-list-item]", inputs: ["activated"], exportAs: ["matListItem"] }, { kind: "directive", type: i2$1.MatListItemLine, selector: "[matListItemLine]" }, { kind: "directive", type: i2$1.MatListItemTitle, selector: "[matListItemTitle]" }, { kind: "ngmodule", type: MatSelectModule }, { kind: "component", type: LayerSelectorComponent, selector: "lib-layer-selector", inputs: ["map", "profile", "currentZoomLevel"] }, { kind: "component", type: MapSearchComponent, selector: "lib-map-search" }, { kind: "component", type: ToolboxComponent, selector: "map-toolbox", inputs: ["map", "showMeasureDistance", "showMeasureArea", "collapsed", "settings", "profile", "WKTInputEnabled", "deleteEnabled"] }, { kind: "component", type: ActiveObjectsComponent, selector: "activeObjects", inputs: ["settings"] }] });
532
1535
  }
533
1536
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.15", ngImport: i0, type: GisKomponentComponent, decorators: [{
534
1537
  type: Component,
535
- args: [{ selector: 'gis-komponent', imports: [CommonModule, MatIconModule, MatListModule, MatSelectModule, LayerSelectorComponent], providers: [LayerHelperService], template: " <lib-layer-selector [map]=\"map\" [profile]=\"selectedProfile\"></lib-layer-selector>\n\n<div class=\"map-container\">\n\n <!-- Objektvisning -->\n <!-- @if (showObjects) {\n <div class=\"object-panel\" [class.collapsed]=\"activeObjectsCollapsed\">\n <div class=\"header\" (click)=\"toggleActiveObjectsCollapsed()\">\n <span>Aktive objekter ({{activeObjects.length}})</span>\n <mat-icon>{{ activeObjectsCollapsed ? 'expand_more' : 'expand_less' }}</mat-icon>\n </div>\n @if (!activeObjectsCollapsed){\n <ul>\n <li *ngFor=\"let obj of activeObjects\">{{ obj.name }} ({{obj.size}})</li>\n </ul>\n }\n </div>\n } -->\n\n <!-- Konflikter -->\n @if (showConflicts) {\n\n <div class=\"conflict-panel\" [class.collapsed]=\"conflictsCollapsed\">\n <div class=\"header\">\n <span>Konflikter (3)</span>\n <mat-icon>{{ activeObjectsCollapsed ? 'expand_more' : 'expand_less' }}</mat-icon> -->\n </div>\n <mat-list>\n <mat-list-item>\n <span matListItemTitle>Lag #1</span>\n <span matListItemLine><mat-icon>highlight</mat-icon> Objekt #1</span>\n </mat-list-item>\n <mat-list-item>\n <span matListItemTitle>Lag #2 (svarer ikke)</span>\n </mat-list-item>\n <mat-list-item>\n <span matListItemTitle>Lag #3</span>\n <span matListItemLine><mat-icon>highlight</mat-icon> Objekt #1</span>\n <span matListItemLine><mat-icon>highlight</mat-icon> Objekt #2</span>\n </mat-list-item>\n </mat-list>\n </div>\n \n } \n\n <!-- Kort -->\n <div id=\"map\" class=\"map\"></div>\n</div>\n", styles: ["::ng-deep .map-container{position:relative;height:85vh;width:100%}::ng-deep #map{width:100%;height:100%;position:absolute;inset:0}::ng-deep ::ng-deep .ol-logo{position:absolute;left:auto;right:3em;top:6.25em}::ng-deep ::ng-deep .ol-copyright{position:absolute;left:auto;right:calc(50vw - 90px);bottom:.5em}::ng-deep ::ng-deep .toolbar{position:absolute;top:10px;left:10px;background:#fff;padding:4px;border-radius:4px;display:flex;flex-direction:column;transition:width .3s;z-index:1000}::ng-deep ::ng-deep .toolbar.collapsed{width:40px;overflow:hidden}::ng-deep .object-panel{position:absolute;bottom:10px;left:10px;background:#fff;padding:8px;border-radius:4px;z-index:1000}::ng-deep .object-panel .header{display:flex;justify-content:space-between;padding:8px;cursor:pointer;background:#f0f0f0}::ng-deep .conflict-panel{position:absolute;bottom:10px;right:10px;background:#fff;padding:8px;border-radius:4px;z-index:1000}::ng-deep .conflict-panel .header{display:flex;justify-content:space-between;padding:8px;cursor:pointer;background:#f0f0f0}::ng-deep ::ng-deep .ol-zoom.ol-unselectable.ol-control{display:flex;flex-direction:column;position:absolute;top:2rem;left:1rem}::ng-deep ::ng-deep button.ol-zoom-in{font-size:28px;width:35px;height:35px;background-color:#fff;border:1px solid rgba(255,255,255,.8);box-shadow:#0000004d 0 1px 4px -1px;cursor:pointer;color:#334155}::ng-deep button.ol-zoom-out{font-size:28px;width:35px;height:35px;background-color:#fff;border:1px solid rgba(255,255,255,.8);box-shadow:#0000004d 0 1px 4px -1px;cursor:pointer;color:#334155}::ng-deep button.ol-rotate-reset{font-size:28px;width:35px;height:35px;background-color:#fff;border:1px solid rgba(255,255,255,.8);box-shadow:#0000004d 0 1px 4px -1px;cursor:pointer;color:#334155;margin-top:10px}::ng-deep .ol-scale-bar.ol-unselectable{position:absolute;bottom:1rem;left:1rem}\n"] }]
1538
+ args: [{ selector: 'gis-komponent', imports: [CommonModule, MatIconModule, MatListModule, MatSelectModule, LayerSelectorComponent, MapSearchComponent, ToolboxComponent, ActiveObjectsComponent], providers: [LayerHelperService], template: "<lib-layer-selector [map]=\"map\" [profile]=\"selectedProfile\" [currentZoomLevel]=\"currentZoomLevel\"></lib-layer-selector>\n<span>Zoom: {{ currentZoomLevel}}</span>\n<div #activeObjectsRef>\n @if(showActiveObjects && settings) {\n <activeObjects [settings]=\"settings\"></activeObjects>\n }\n</div>\n<div #toolbarRef class=\"map-toolbar-container\">\n @if(showToolbar && settings) {\n <map-toolbox [map]=\"map\" \n [profile]=\"selectedProfile\"\n [settings]=\"settings\"\n [collapsed]=\"selectedProfile.toolbarCollapsed\" [WKTInputEnabled]=\"settings?.WKTInputEnabled\" [deleteEnabled]=\"settings?.deleteEnabled\" [showMeasureArea]=\"selectedProfile.showAreaMeasurement\" [showMeasureDistance]=\"selectedProfile.showDistanceMeasurement\"></map-toolbox>\n }\n</div>\n\n<div class=\"map-container\">\n <lib-map-search></lib-map-search>\n\n <!-- Objektvisning -->\n <!-- @if (showObjects) {\n <div class=\"object-panel\" [class.collapsed]=\"activeObjectsCollapsed\">\n <div class=\"header\" (click)=\"toggleActiveObjectsCollapsed()\">\n <span>Aktive objekter ({{activeObjects.length}})</span>\n <mat-icon>{{ activeObjectsCollapsed ? 'expand_more' : 'expand_less' }}</mat-icon>\n </div>\n @if (!activeObjectsCollapsed){\n <ul>\n <li *ngFor=\"let obj of activeObjects\">{{ obj.name }} ({{obj.size}})</li>\n </ul>\n }\n </div>\n } -->\n\n <!-- Konflikter -->\n @if (showConflicts) {\n\n <div class=\"conflict-panel\" [class.collapsed]=\"conflictsCollapsed\">\n <div class=\"header\">\n <span>Konflikter (3)</span>\n <mat-icon>{{ activeObjectsCollapsed ? 'expand_more' : 'expand_less' }}</mat-icon> -->\n </div>\n <mat-list>\n <mat-list-item>\n <span matListItemTitle>Lag #1</span>\n <span matListItemLine><mat-icon>highlight</mat-icon> Objekt #1</span>\n </mat-list-item>\n <mat-list-item>\n <span matListItemTitle>Lag #2 (svarer ikke)</span>\n </mat-list-item>\n <mat-list-item>\n <span matListItemTitle>Lag #3</span>\n <span matListItemLine><mat-icon>highlight</mat-icon> Objekt #1</span>\n <span matListItemLine><mat-icon>highlight</mat-icon> Objekt #2</span>\n </mat-list-item>\n </mat-list>\n </div>\n \n } \n\n <!-- Kort -->\n <div id=\"map\" class=\"map\"></div>\n \n\n</div>\n", styles: ["::ng-deep .lib-error-snackbar{background-color:#d32f2f;color:#fff;font-weight:500}::ng-deep .map-container{position:relative;height:85vh;width:100%}::ng-deep #map{width:100%;height:100%;position:absolute;inset:0}::ng-deep ::ng-deep .ol-logo{position:absolute;left:auto;right:3em;top:6.25em}::ng-deep ::ng-deep .ol-copyright{position:absolute;left:auto;right:calc(50vw - 90px);bottom:.5em}::ng-deep ::ng-deep .toolbar{position:absolute;top:10px;left:10px;background:#fff;padding:4px;border-radius:4px;display:flex;flex-direction:column;transition:width .3s;z-index:1000}::ng-deep ::ng-deep .toolbar.collapsed{width:40px;overflow:hidden}::ng-deep .object-panel{position:absolute;bottom:10px;left:10px;background:#fff;padding:8px;border-radius:4px;z-index:1000}::ng-deep .object-panel .header{display:flex;justify-content:space-between;padding:8px;cursor:pointer;background:#f0f0f0}::ng-deep .conflict-panel{position:absolute;bottom:10px;right:10px;background:#fff;padding:8px;border-radius:4px;z-index:1000}::ng-deep .conflict-panel .header{display:flex;justify-content:space-between;padding:8px;cursor:pointer;background:#f0f0f0}::ng-deep ::ng-deep .ol-zoom.ol-unselectable.ol-control{display:flex;flex-direction:column;position:absolute;top:2rem;left:1rem}::ng-deep ::ng-deep button.ol-zoom-in{font-size:2em;width:40px;height:40px;background-color:#fff;border:1px solid rgba(255,255,255,.8);box-shadow:#0000004d 0 1px 4px -1px;cursor:pointer;color:#666}::ng-deep button.ol-zoom-out{font-size:2em;width:40px;height:40px;background-color:#fff;border:1px solid rgba(255,255,255,.8);box-shadow:#0000004d 0 1px 4px -1px;cursor:pointer;color:#666}::ng-deep button.ol-rotate-reset{font-size:28px;width:35px;height:35px;background-color:#fff;border:1px solid rgba(255,255,255,.8);box-shadow:#0000004d 0 1px 4px -1px;cursor:pointer;color:#334155;margin-top:10px}::ng-deep .ol-scale-bar.ol-unselectable{position:absolute;bottom:1rem;left:1rem}\n"] }]
536
1539
  }], propDecorators: { identifier: [{
537
1540
  type: Input
538
1541
  }], settings: [{
539
1542
  type: Input
1543
+ }], toolbarRef: [{
1544
+ type: ViewChild,
1545
+ args: ['toolbarRef', { static: true }]
1546
+ }], activeObjectsRef: [{
1547
+ type: ViewChild,
1548
+ args: ['activeObjectsRef', { static: true }]
540
1549
  }] } });
541
1550
 
1551
+ const libErrorInterceptor = (req, next) => {
1552
+ const errorService = inject(LibErrorService);
1553
+ return next(req).pipe(catchError((error) => {
1554
+ errorService.log(error);
1555
+ errorService.show(error);
1556
+ return throwError(() => error); // Let the component decide if needed
1557
+ }));
1558
+ };
1559
+
542
1560
  function provideLibrary(config) {
543
1561
  return [
544
1562
  { provide: GISKOMPONENT_CONFIG, useValue: config }
545
1563
  ];
546
1564
  }
1565
+ function provideLibraryHttpClient() {
1566
+ return [
1567
+ provideHttpClient(withInterceptors([libErrorInterceptor]))
1568
+ ];
1569
+ }
1570
+ ;
547
1571
 
548
1572
  /*
549
1573
  * Public API Surface of gis-komponent
@@ -553,5 +1577,5 @@ function provideLibrary(config) {
553
1577
  * Generated bundle index. Do not edit.
554
1578
  */
555
1579
 
556
- export { GisKomponentComponent, LayerHelperService, ProfileService, provideLibrary };
1580
+ export { GisKomponentComponent, LayerHelperService, LibErrorService, ProfileService, provideLibrary, provideLibraryHttpClient };
557
1581
  //# sourceMappingURL=regionerne-gis-komponent.mjs.map