@geogirafe/lib-geoportal 1.0.2149179221 → 1.0.2152254142

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.
@@ -217,6 +217,7 @@
217
217
  "Message sent": "Nachricht gesendet",
218
218
  "meters": "Meter",
219
219
  "Move": "Verschieben",
220
+ "Move Shape": "Zeichnung verschieben",
220
221
  "My Drawing": "Meine Zeichnung",
221
222
  "My selection": "Meine Auswahl",
222
223
  "Name": "Name",
@@ -309,7 +310,9 @@
309
310
  "Remove filter": "Filter entfernen",
310
311
  "Remove group": "Gruppe entfernen",
311
312
  "Remove layer": "Layer entfernen",
313
+ "Remove Shape": "Zeichnung entfernen",
312
314
  "Remove theme": "Thema entfernen",
315
+ "Remove Vertex": "Eckpunkt entfernen",
313
316
  "Report an error": "Einen Fehler melden",
314
317
  "Request secure access": "Sicheren Zugang anfordern",
315
318
  "Reset All": "Alle zurücksetzen",
@@ -317,6 +320,7 @@
317
320
  "Reset filter": "Filter zurücksetzen",
318
321
  "Reset preferences": "Einstellungen zurücksetzen",
319
322
  "Reset user preferences back to default values?": "Einstellungen zurück auf Standardwerte setzen?",
323
+ "Rotate/Scale Shape": "Zeichnung rotieren/skalieren",
320
324
  "Rotation": "Rotation",
321
325
  "same as system": "Systemeinstellung",
322
326
  "Save": "Speichern",
@@ -218,6 +218,7 @@
218
218
  "Message sent": "Message sent",
219
219
  "meters": "meters",
220
220
  "Move": "Move",
221
+ "Move Shape": "Move Shape",
221
222
  "My Drawing": "My Drawing",
222
223
  "My selection": "My selection",
223
224
  "Name": "Name",
@@ -310,7 +311,9 @@
310
311
  "Remove filter": "Remove filter",
311
312
  "Remove group": "Remove group",
312
313
  "Remove layer": "Remove Layer",
314
+ "Remove Shape": "Remove Shape",
313
315
  "Remove theme": "Remove theme",
316
+ "Remove Vertex": "Remove vertex",
314
317
  "Report an error": "Report an error",
315
318
  "Request secure access": "Request secure access",
316
319
  "Reset All": "Reset All",
@@ -318,6 +321,7 @@
318
321
  "Reset filter": "Reset filter",
319
322
  "Reset preferences": "Reset preferences",
320
323
  "Reset user preferences back to default values?": "Reset user preferences back to default values?",
324
+ "Rotate/Scale Shape": "Rotate/Scale Shape",
321
325
  "Rotation": "Rotation",
322
326
  "same as system": "Same as system",
323
327
  "Save": "Save",
@@ -217,6 +217,7 @@
217
217
  "Message sent": "Message envoyé",
218
218
  "meters": "mètres",
219
219
  "Move": "Déplacer",
220
+ "Move Shape": "Déplacer la forme",
220
221
  "My Drawing": "Mon dessin",
221
222
  "My selection": "Ma sélection",
222
223
  "Name": "Nom",
@@ -309,7 +310,9 @@
309
310
  "Remove filter": "Supprimer le filtre",
310
311
  "Remove group": "Supprimer le groupe",
311
312
  "Remove layer": "Supprimer la couche",
313
+ "Remove Shape": "Supprimer la forme",
312
314
  "Remove theme": "Supprimer le thème",
315
+ "Remove Vertex": "Supprimer le sommet",
313
316
  "Report an error": "Signaler un problème",
314
317
  "Request secure access": "Demander un accès sécurisé",
315
318
  "Reset All": "Réinitialiser tout",
@@ -317,6 +320,7 @@
317
320
  "Reset filter": "Réinitialiser le filtre",
318
321
  "Reset preferences": "Réinitialiser les préférences",
319
322
  "Reset user preferences back to default values?": "Réinitialiser tous les paramètres à la valeur par défaut?",
323
+ "Rotate/Scale Shape": "Faire pivoter/redimensionner la forme",
320
324
  "Rotation": "Rotation",
321
325
  "same as system": "Identique au système",
322
326
  "Save": "Enregistrer",
@@ -217,6 +217,7 @@
217
217
  "Message sent": "Messaggio inviato",
218
218
  "meters": "metri",
219
219
  "Move": "Muovi",
220
+ "Move Shape": "Sposta forma",
220
221
  "My Drawing": "Il mio disegno",
221
222
  "My selection": "La mia selezione",
222
223
  "Name": "Nome",
@@ -309,7 +310,9 @@
309
310
  "Remove filter": "Rimuovi filtro",
310
311
  "Remove group": "Rimuovi gruppo",
311
312
  "Remove layer": "Rimuovi layer",
313
+ "Remove Shape": "Rimuovi forma",
312
314
  "Remove theme": "Rimuovi tema",
315
+ "Remove Vertex": "Rimuovi vertice",
313
316
  "Report an error": "Segnala un errore",
314
317
  "Request secure access": "Richiedi accesso sicuro",
315
318
  "Reset All": "Azzeramento di tutti",
@@ -317,6 +320,7 @@
317
320
  "Reset filter": "Azzeramento del filtro",
318
321
  "Reset preferences": "Reimposta preferenze",
319
322
  "Reset user preferences back to default values?": "Ripristinare tutte le impostazioni al valore predefinito?",
323
+ "Rotate/Scale Shape": "Ruota/Ridimensiona forma",
320
324
  "Rotation": "Rotazione",
321
325
  "same as system": "Come il sistema",
322
326
  "Save": "Salva",
@@ -186,7 +186,12 @@ input{border:1px solid var(--bkg-color-grad1);width:inherit;background-color:var
186
186
  }
187
187
  this.drawingState = this.state.extendedState.drawing;
188
188
  const map = this.context.componentManager.getComponents(MapComponent)[0];
189
- this.olDrawing = new OlDrawing(map, this.name, this.context);
189
+ this.olDrawing = new OlDrawing(map, this.name, this.context, (featureId) => {
190
+ const drawingFeature = this.drawingState.features.find((drawingFeature) => drawingFeature.id === featureId);
191
+ if (drawingFeature) {
192
+ this.deleteFeature(drawingFeature);
193
+ }
194
+ });
190
195
  this.cesiumDrawing = new CesiumDrawing(map, this.name, this.context);
191
196
  this.render();
192
197
  this.subscribe('extendedState.drawing.features', (olds, news) => this.onFeaturesChanged(olds, news));
@@ -294,6 +299,7 @@ input{border:1px solid var(--bkg-color-grad1);width:inherit;background-color:var
294
299
  this.refreshRender();
295
300
  }
296
301
  onToggleFeatureSelection(feature) {
302
+ this.setTool(null);
297
303
  feature.selected = !feature.selected;
298
304
  this.refreshRender();
299
305
  }
@@ -329,7 +335,9 @@ input{border:1px solid var(--bkg-color-grad1);width:inherit;background-color:var
329
335
  return !this.selectedFeatures.every((f) => f.isPointOrPolyline());
330
336
  }
331
337
  async deleteFeature(feature) {
332
- const confirm = await window.gConfirm(`Do you want to remove "${feature.name}" ?`, 'Delete Feature');
338
+ const confirm = await window.gConfirm('Do you want to remove "${feature.name}" ?', 'Delete Feature', (translatedMessage) => {
339
+ return translatedMessage.replace('${feature.name}', feature.name);
340
+ });
333
341
  if (confirm) {
334
342
  const idx = this.drawingState.features.findIndex((f) => f.id === feature.id);
335
343
  if (idx > -1) {
@@ -4,17 +4,19 @@ import { Collection, Feature } from 'ol';
4
4
  import { Geometry, Polygon, SimpleGeometry } from 'ol/geom';
5
5
  import { SketchCoordType } from 'ol/interaction/Draw';
6
6
  import { Style } from 'ol/style';
7
- import { Modify, Snap, Draw } from 'ol/interaction';
7
+ import { Draw, Modify, Snap, Translate } from 'ol/interaction';
8
8
  import VectorSource, { VectorSourceEvent } from 'ol/source/Vector';
9
9
  import VectorLayer from 'ol/layer/Vector';
10
10
  import { Projection } from 'ol/proj';
11
11
  import { Coordinate } from 'ol/coordinate';
12
12
  import { ContextMenu } from '../map/tools/contextmenu';
13
13
  import IGirafeContext from '../../tools/context/icontext';
14
+ import { StyleFunction } from 'ol/style/Style';
14
15
  export default class OlDrawing {
15
16
  private readonly map;
16
17
  private readonly toolName;
17
18
  private readonly context;
19
+ private readonly deleteHandler;
18
20
  modifiableFeatures: Collection<Feature>;
19
21
  draw: Draw | null;
20
22
  modify: Modify | null;
@@ -24,12 +26,21 @@ export default class OlDrawing {
24
26
  fixedLength: number;
25
27
  drawingSource: VectorSource;
26
28
  drawingLayer: VectorLayer;
29
+ lastClosestFeature: Feature | null;
30
+ translate: Translate | null;
31
+ rotateAndScale: Modify | null;
32
+ defaultStyle: StyleFunction | undefined;
33
+ private readonly updateGeometryInState;
27
34
  private get state();
28
35
  private get drawingState();
29
36
  private get config();
30
- constructor(map: MapComponent, toolName: string, context: IGirafeContext);
37
+ constructor(map: MapComponent, toolName: string, context: IGirafeContext, deleteHandler: (featureId: string) => void);
31
38
  private addModifyInteraction;
32
39
  private addSnapInteraction;
40
+ private addTranslateInteraction;
41
+ private removeTranslateInteraction;
42
+ private addRotateAndScaleInteraction;
43
+ private removeRotateAndScaleInteraction;
33
44
  /**
34
45
  * Adds a context menu to the map with a single entry 'remove vertex'. The menu is configured to open
35
46
  * when the user does an alternate click ( = context event) on or near a vertex of a modifiable feature.
@@ -37,10 +48,13 @@ export default class OlDrawing {
37
48
  private addEditContextMenu;
38
49
  addEditInteractions(): void;
39
50
  removeEditInteractions(): void;
51
+ private readonly prepareMenuEntriesForVertex;
52
+ private readonly prepareMenuEntriesForShape;
40
53
  /**
41
54
  Check if there is a vertex of a selected (=editable) feature within the pixel tolerance of the clicked coordinates.
42
55
  */
43
56
  hasEditableVertexAtCoordinate(coordinate: Coordinate): boolean;
57
+ hasEditableShapeAtCoordinate(coordinate: Coordinate): boolean;
44
58
  /**
45
59
  Returns the closest vertex and feature to a coordinate from the drawing source.
46
60
  The feature source can be pre-filtered via an optional filter function.
@@ -51,6 +65,7 @@ export default class OlDrawing {
51
65
  modify option properties 'condition' and 'insertVertexCondition'.
52
66
  */
53
67
  private removeLastInteractedVertex;
68
+ private removeLastInteractedShape;
54
69
  /**
55
70
  * Adds features to the drawing source if they are missing and updates their style each time a property changes.
56
71
  * Adding them to the source is only necessary if the feature originates from a deserialized state and not
@@ -1,19 +1,21 @@
1
1
  import DrawingFeature, { DrawingShape } from './drawingFeature';
2
2
  import { Collection, Feature } from 'ol';
3
- import { Geometry, LineString, Point, Polygon, Circle as CircleGeom, MultiPoint, MultiLineString, LinearRing, MultiPolygon } from 'ol/geom';
3
+ import { Circle as CircleGeom, Geometry, LinearRing, LineString, MultiLineString, MultiPoint, MultiPolygon, Point, Polygon } from 'ol/geom';
4
4
  import { createBox, createRegularPolygon } from 'ol/interaction/Draw';
5
- import { Style, Stroke, Text, Fill, RegularShape, Circle } from 'ol/style';
6
- import { Modify, Snap, Draw } from 'ol/interaction';
5
+ import { Circle, Fill, RegularShape, Stroke, Style, Text } from 'ol/style';
6
+ import { Draw, Modify, Snap, Translate } from 'ol/interaction';
7
7
  import VectorSource from 'ol/source/Vector';
8
8
  import VectorLayer from 'ol/layer/Vector';
9
9
  import GeoJSON from 'ol/format/GeoJSON';
10
10
  import { getPointResolution } from 'ol/proj';
11
- import { never, noModifierKeys, primaryAction } from 'ol/events/condition';
12
- import { getDistance, getArea } from '../../tools/utils/olutils';
13
- import { ContextMenu } from '../map/tools/contextmenu';
11
+ import { never, primaryAction } from 'ol/events/condition';
12
+ import { getArea, getDistance } from '../../tools/utils/olutils';
13
+ import { ContextMenu, EntryInteractionType } from '../map/tools/contextmenu';
14
14
  import { formatCoordinates } from '../../tools/geometrytools';
15
15
  import { v4 as uuidv4 } from 'uuid';
16
16
  import { isAlternateMouseClick, isPrimaryPointerAction } from '../../tools/state/userinteractionevent';
17
+ import { getCenter, getHeight, getWidth } from 'ol/extent';
18
+ import CircleStyle from 'ol/style/Circle';
17
19
  function getLineStroke(strokeType, lineWidth) {
18
20
  switch (strokeType) {
19
21
  case 'full':
@@ -49,10 +51,51 @@ function extractVerticesFromGeometry(geometry) {
49
51
  }
50
52
  return new MultiPoint(vertices);
51
53
  }
54
+ function calculateCenter(geometry) {
55
+ let center, coordinates, minRadius;
56
+ if (geometry instanceof Polygon) {
57
+ let x = 0;
58
+ let y = 0;
59
+ let i = 0;
60
+ coordinates = geometry.getCoordinates()[0].slice();
61
+ for (const coordinate of coordinates) {
62
+ x += coordinate[0];
63
+ y += coordinate[1];
64
+ i++;
65
+ }
66
+ center = [x / i, y / i];
67
+ }
68
+ else if (geometry instanceof LineString) {
69
+ center = geometry.getCoordinateAt(0.5);
70
+ coordinates = geometry.getCoordinates();
71
+ }
72
+ else {
73
+ center = getCenter(geometry.getExtent());
74
+ }
75
+ let sqDistances;
76
+ if (coordinates) {
77
+ sqDistances = coordinates.map(function (coordinate) {
78
+ const dx = coordinate[0] - center[0];
79
+ const dy = coordinate[1] - center[1];
80
+ return dx * dx + dy * dy;
81
+ });
82
+ minRadius = Math.sqrt(Math.max(...sqDistances)) / 3;
83
+ }
84
+ else {
85
+ minRadius = Math.max(getWidth(geometry.getExtent()), getHeight(geometry.getExtent())) / 3;
86
+ }
87
+ return {
88
+ center: center,
89
+ coordinates: coordinates,
90
+ minRadius: minRadius,
91
+ sqDistances: sqDistances
92
+ };
93
+ }
52
94
  export default class OlDrawing {
53
95
  map;
54
96
  toolName;
55
97
  context;
98
+ deleteHandler;
56
99
  modifiableFeatures = new Collection([]);
57
100
  draw = null;
58
101
  modify = null;
@@ -62,6 +105,17 @@ export default class OlDrawing {
62
105
  fixedLength = 0;
63
106
  drawingSource;
64
107
  drawingLayer;
108
+ lastClosestFeature = null;
109
+ translate = null;
110
+ rotateAndScale = null;
111
+ defaultStyle;
112
+ updateGeometryInState = (olFeature) => {
113
+ const idx = this.drawingState.features.findIndex((f) => f.id === olFeature.getId());
114
+ const drawingFeature = this.drawingState.features[idx];
115
+ if (idx > -1) {
116
+ this.drawingState.features[idx].geojson = DrawingFeature.geojsonFromOlFeature(olFeature, drawingFeature.type);
117
+ }
118
+ };
65
119
  get state() {
66
120
  return this.context.stateManager.state;
67
121
  }
@@ -71,10 +125,11 @@ export default class OlDrawing {
71
125
  get config() {
72
126
  return this.context.configManager.Config;
73
127
  }
74
- constructor(map, toolName, context) {
128
+ constructor(map, toolName, context, deleteHandler) {
75
129
  this.map = map;
76
130
  this.toolName = toolName;
77
131
  this.context = context;
132
+ this.deleteHandler = deleteHandler;
78
133
  this.drawingSource = new VectorSource({ features: new Collection() });
79
134
  this.drawingSource.on('addfeature', (e) => this.onFeatureAdded(e));
80
135
  this.drawingLayer = new VectorLayer({
@@ -85,6 +140,7 @@ export default class OlDrawing {
85
140
  altitudeMode: 'clampToGround'
86
141
  }
87
142
  });
143
+ this.defaultStyle = new Modify({ source: this.drawingSource }).getOverlay().getStyleFunction();
88
144
  this.map.subscribe('extendedState.drawing.activeTool', (_oldTool, newTool) => newTool === null ? this.removeDrawInteraction() : this.addDrawInteraction(newTool));
89
145
  // Could be useful logic if we decide that the mobile UI should rely use a dedicated button
90
146
  // to remove a selected vetex from a polygon/line (rather than using a longpress context menu)
@@ -127,13 +183,7 @@ export default class OlDrawing {
127
183
  this.map.olMap.addInteraction(this.modify);
128
184
  // Update the modified geometries in the state
129
185
  this.modify.on('modifyend', (e) => {
130
- e.features.forEach((olFeature) => {
131
- const idx = this.drawingState.features.findIndex((f) => f.id === olFeature.getId());
132
- const drawingFeature = this.drawingState.features[idx];
133
- if (idx > -1) {
134
- this.drawingState.features[idx].geojson = DrawingFeature.geojsonFromOlFeature(olFeature, drawingFeature.type);
135
- }
136
- });
186
+ e.features.forEach((olFeature) => this.updateGeometryInState(olFeature)); //NOSONAR(typescript:S7728) Collection<?> is a custom Implementation with custom forEach
137
187
  });
138
188
  }
139
189
  addSnapInteraction() {
@@ -142,6 +192,110 @@ export default class OlDrawing {
142
192
  this.snap = new Snap({ source: this.drawingSource, pixelTolerance: this.map.pixelTolerance });
143
193
  this.map.olMap.addInteraction(this.snap);
144
194
  }
195
+ addTranslateInteraction() {
196
+ this.removeTranslateInteraction();
197
+ this.translate = new Translate({
198
+ condition: (event) => {
199
+ return this.translate != null && primaryAction(event);
200
+ },
201
+ layers: [this.drawingLayer]
202
+ });
203
+ this.map.olMap.addInteraction(this.translate);
204
+ }
205
+ removeTranslateInteraction() {
206
+ if (this.translate) {
207
+ this.map.olMap.removeInteraction(this.translate);
208
+ this.translate = null;
209
+ }
210
+ }
211
+ addRotateAndScaleInteraction() {
212
+ this.removeRotateAndScaleInteraction();
213
+ const defaultStyle = this.defaultStyle;
214
+ const getCoordinates = (feature) => {
215
+ const geometry = feature.getGeometry();
216
+ if (geometry instanceof Point) {
217
+ return geometry.getCoordinates();
218
+ }
219
+ else if (geometry instanceof LineString) {
220
+ return geometry.getCoordinates()[0];
221
+ }
222
+ else if (geometry instanceof Polygon) {
223
+ return geometry.getCoordinates()[0][0];
224
+ }
225
+ return [];
226
+ };
227
+ this.rotateAndScale = new Modify({
228
+ features: this.modifiableFeatures,
229
+ condition: (event) => {
230
+ return this.rotateAndScale != null && primaryAction(event);
231
+ },
232
+ deleteCondition: never,
233
+ insertVertexCondition: never,
234
+ style: function (feature, resolution) {
235
+ feature.get('features').forEach(function (modifyFeature) {
236
+ const modifyGeometry = modifyFeature.get('modifyGeometry');
237
+ if (modifyGeometry) {
238
+ const point = getCoordinates(feature);
239
+ let modifyPoint = modifyGeometry.point;
240
+ if (!modifyPoint) {
241
+ // save the initial geometry and vertex position
242
+ modifyPoint = point;
243
+ modifyGeometry.point = modifyPoint;
244
+ modifyGeometry.geometry0 = modifyGeometry.geometry;
245
+ // get anchor and minimum radius of vertices to be used
246
+ const result = calculateCenter(modifyGeometry.geometry0);
247
+ modifyGeometry.center = result.center;
248
+ modifyGeometry.minRadius = result.minRadius;
249
+ }
250
+ const center = modifyGeometry.center;
251
+ const minRadius = modifyGeometry.minRadius;
252
+ let dx, dy;
253
+ dx = modifyPoint[0] - center[0];
254
+ dy = modifyPoint[1] - center[1];
255
+ const initialRadius = Math.hypot(dx, dy);
256
+ if (initialRadius > minRadius) {
257
+ const initialAngle = Math.atan2(dy, dx);
258
+ dx = point[0] - center[0];
259
+ dy = point[1] - center[1];
260
+ const currentRadius = Math.hypot(dx, dy);
261
+ if (currentRadius > 0) {
262
+ const currentAngle = Math.atan2(dy, dx);
263
+ const geometry = modifyGeometry.geometry0.clone();
264
+ geometry.scale(currentRadius / initialRadius, undefined, center);
265
+ geometry.rotate(currentAngle - initialAngle, center);
266
+ modifyGeometry.geometry = geometry;
267
+ }
268
+ }
269
+ }
270
+ });
271
+ return defaultStyle(feature, resolution);
272
+ }
273
+ });
274
+ this.rotateAndScale.on('modifystart', function (event) {
275
+ // prettier-ignore
276
+ event.features.forEach(function (feature) {
277
+ feature.set('modifyGeometry', { geometry: feature.getGeometry().clone() }, true);
278
+ });
279
+ });
280
+ this.rotateAndScale.on('modifyend', (event) => {
281
+ // prettier-ignore
282
+ event.features.forEach((olFeature) => {
283
+ const modifyGeometry = olFeature.get('modifyGeometry');
284
+ if (modifyGeometry) {
285
+ olFeature.setGeometry(modifyGeometry.geometry);
286
+ olFeature.unset('modifyGeometry', true);
287
+ this.updateGeometryInState(olFeature);
288
+ }
289
+ });
290
+ });
291
+ this.map.olMap.addInteraction(this.rotateAndScale);
292
+ }
293
+ removeRotateAndScaleInteraction() {
294
+ if (this.rotateAndScale) {
295
+ this.map.olMap.removeInteraction(this.rotateAndScale);
296
+ this.rotateAndScale = null;
297
+ }
298
+ }
145
299
  /**
146
300
  * Adds a context menu to the map with a single entry 'remove vertex'. The menu is configured to open
147
301
  * when the user does an alternate click ( = context event) on or near a vertex of a modifiable feature.
@@ -150,7 +304,7 @@ export default class OlDrawing {
150
304
  this.removeEditContextMenu();
151
305
  const menuEntries = [
152
306
  {
153
- entry: 'Remove vertex',
307
+ entry: 'Remove Vertex',
154
308
  callback: (_evt, mapCoordinate) => {
155
309
  const successful = this.removeLastInteractedVertex();
156
310
  if (!successful) {
@@ -161,7 +315,43 @@ export default class OlDrawing {
161
315
  type: 'warning'
162
316
  });
163
317
  }
164
- }
318
+ },
319
+ prepare: this.prepareMenuEntriesForVertex
320
+ },
321
+ {
322
+ entry: 'Move Shape',
323
+ callback: (_evt, _mapCoordinate) => {
324
+ if (this.lastClosestFeature) {
325
+ this.removeModifyInteraction();
326
+ this.addTranslateInteraction();
327
+ this.map.olMap.on('singleclick', () => {
328
+ this.removeTranslateInteraction();
329
+ this.addModifyInteraction();
330
+ });
331
+ }
332
+ },
333
+ prepare: this.prepareMenuEntriesForShape
334
+ },
335
+ {
336
+ entry: 'Rotate/Scale Shape',
337
+ callback: (_evt, _mapCoordinate) => {
338
+ if (this.lastClosestFeature) {
339
+ this.removeModifyInteraction();
340
+ this.addRotateAndScaleInteraction();
341
+ this.map.olMap.on('singleclick', () => {
342
+ this.removeRotateAndScaleInteraction();
343
+ this.addModifyInteraction();
344
+ });
345
+ }
346
+ },
347
+ prepare: this.prepareMenuEntriesForShape
348
+ },
349
+ {
350
+ entry: 'Remove Shape',
351
+ callback: (_evt, _mapCoordinate) => {
352
+ this.removeLastInteractedShape();
353
+ },
354
+ prepare: this.prepareMenuEntriesForShape
165
355
  }
166
356
  ];
167
357
  const conditionToOpen = (_evt, mapCoordinate) => {
@@ -169,7 +359,7 @@ export default class OlDrawing {
169
359
  return false;
170
360
  }
171
361
  // Only proceed if there is an editable vertex under the mouse pointer
172
- return this.hasEditableVertexAtCoordinate(mapCoordinate);
362
+ return this.hasEditableShapeAtCoordinate(mapCoordinate) || this.hasEditableVertexAtCoordinate(mapCoordinate);
173
363
  };
174
364
  this.editContextMenu = new ContextMenu(this.context, menuEntries, true, conditionToOpen);
175
365
  }
@@ -190,6 +380,16 @@ export default class OlDrawing {
190
380
  this.removeSnapInteraction();
191
381
  }
192
382
  }
383
+ prepareMenuEntriesForVertex = (_e, mapCoordinate) => {
384
+ return this.hasEditableVertexAtCoordinate(mapCoordinate)
385
+ ? EntryInteractionType.ENABLED
386
+ : EntryInteractionType.NOT_SHOWN;
387
+ };
388
+ prepareMenuEntriesForShape = (_e, mapCoordinate) => {
389
+ return this.hasEditableShapeAtCoordinate(mapCoordinate)
390
+ ? EntryInteractionType.ENABLED
391
+ : EntryInteractionType.NOT_SHOWN;
392
+ };
193
393
  /**
194
394
  Check if there is a vertex of a selected (=editable) feature within the pixel tolerance of the clicked coordinates.
195
395
  */
@@ -203,13 +403,23 @@ export default class OlDrawing {
203
403
  const clickAsPixel = this.map.olMap.getPixelFromCoordinate(coordinate);
204
404
  const dx = vertexAsPixel[0] - clickAsPixel[0];
205
405
  const dy = vertexAsPixel[1] - clickAsPixel[1];
206
- const distanceToVertex = Math.sqrt(dx * dx + dy * dy);
406
+ const distanceToVertex = Math.hypot(dx, dy);
207
407
  if (distanceToVertex < this.map.pixelTolerance) {
208
408
  return true;
209
409
  }
210
410
  }
211
411
  return false;
212
412
  }
413
+ hasEditableShapeAtCoordinate(coordinate) {
414
+ const filter = (olFeature) => this.modifiableFeatures.getArray().some((editFeature) => olFeature == editFeature);
415
+ // Use filter to only query currently selected features
416
+ const closestElements = this.getClosestVertexAndFeature(coordinate, filter);
417
+ if (closestElements) {
418
+ const closestFeature = closestElements[1];
419
+ return closestFeature.getGeometry()?.containsXY(coordinate[0], coordinate[1]) ?? false;
420
+ }
421
+ return false;
422
+ }
213
423
  /**
214
424
  Returns the closest vertex and feature to a coordinate from the drawing source.
215
425
  The feature source can be pre-filtered via an optional filter function.
@@ -218,10 +428,12 @@ export default class OlDrawing {
218
428
  const olFeature = this.drawingSource.getClosestFeatureToCoordinate(coordinate, filter);
219
429
  const geometry = olFeature?.getGeometry();
220
430
  if (geometry && olFeature) {
431
+ this.lastClosestFeature = olFeature;
221
432
  const vertices = extractVerticesFromGeometry(geometry);
222
433
  const closestVertex = vertices.getClosestPoint(coordinate);
223
434
  return [closestVertex, olFeature];
224
435
  }
436
+ this.lastClosestFeature = null;
225
437
  return undefined;
226
438
  }
227
439
  /**
@@ -231,6 +443,14 @@ export default class OlDrawing {
231
443
  removeLastInteractedVertex() {
232
444
  return this.modify?.removePoint();
233
445
  }
446
+ removeLastInteractedShape() {
447
+ if (this.lastClosestFeature) {
448
+ this.deleteHandler(this.lastClosestFeature.getId());
449
+ this.lastClosestFeature = null;
450
+ return true;
451
+ }
452
+ return false;
453
+ }
234
454
  /**
235
455
  * Adds features to the drawing source if they are missing and updates their style each time a property changes.
236
456
  * Adding them to the source is only necessary if the feature originates from a deserialized state and not
@@ -239,7 +459,7 @@ export default class OlDrawing {
239
459
  * @param {DrawingFeature[]} dFeatures - An array of `DrawingFeature` objects to be added.
240
460
  */
241
461
  addFeatures(dFeatures) {
242
- dFeatures.forEach((df) => {
462
+ for (const df of dFeatures) {
243
463
  let olFeature = this.getOlFeatureFromDrawingSource(df.id);
244
464
  if (olFeature === null) {
245
465
  olFeature = this.createOlFeature(df);
@@ -247,7 +467,7 @@ export default class OlDrawing {
247
467
  }
248
468
  df.onChange = (df) => olFeature.setStyle((f) => this.getStyle(df, f));
249
469
  df.onChange(df);
250
- });
470
+ }
251
471
  }
252
472
  /**
253
473
  * Deletes the provided features from the drawing source.
@@ -255,12 +475,12 @@ export default class OlDrawing {
255
475
  * @param {DrawingFeature[]} dFeatures - The list of features to be deleted.
256
476
  */
257
477
  deleteFeatures(dFeatures) {
258
- dFeatures.forEach((df) => {
478
+ for (const df of dFeatures) {
259
479
  const toRemove = this.getOlFeatureFromDrawingSource(df.id);
260
480
  if (toRemove !== null) {
261
481
  this.drawingSource.removeFeature(toRemove);
262
482
  }
263
- });
483
+ }
264
484
  this.updateModifiableFeatures();
265
485
  }
266
486
  /**
@@ -269,12 +489,12 @@ export default class OlDrawing {
269
489
  updateModifiableFeatures() {
270
490
  const selectedDrawingFeatures = this.drawingState.features.filter((f) => f.selected);
271
491
  this.modifiableFeatures.clear();
272
- selectedDrawingFeatures.forEach((df) => {
492
+ for (const df of selectedDrawingFeatures) {
273
493
  const olFeature = this.getOlFeatureFromDrawingSource(df.id);
274
494
  if (olFeature) {
275
495
  this.modifiableFeatures.push(olFeature);
276
496
  }
277
- });
497
+ }
278
498
  // Only activate interaction if there are features to modify
279
499
  if (this.modifiableFeatures.getLength() > 0) {
280
500
  this.addEditInteractions();
@@ -397,7 +617,7 @@ export default class OlDrawing {
397
617
  geometryFunction: geomFunction,
398
618
  // Default condition for ol drawing is noModifierKeys(e)
399
619
  // canExecute: If another tool is exclusively drawing, this interaction will be prevented from reacting
400
- condition: (e) => noModifierKeys(e) && this.canExecute('map.draw'),
620
+ condition: (e) => primaryAction(e) && this.canExecute('map.draw'),
401
621
  style: (f) => this.getStyle(new DrawingFeature(tool, this.drawingState, this.config.drawing), f)
402
622
  });
403
623
  this.draw.on('drawend', () => {
@@ -417,11 +637,13 @@ export default class OlDrawing {
417
637
  }
418
638
  // TODO Move as much parameters as possible into DrawingFeature
419
639
  getStyle(dFeature, olFeature) {
420
- const geometry = olFeature.getGeometry();
640
+ const modifyGeometry = olFeature.get('modifyGeometry');
641
+ const geometry = modifyGeometry ? modifyGeometry.geometry : olFeature.getGeometry();
421
642
  const measureFont = 'Bold ' + dFeature.measureFontSize + 'px/1 ' + dFeature.font;
422
643
  const nameFont = 'Bold ' + dFeature.nameFontSize + 'px/1 ' + dFeature.font;
423
644
  const measureColor = 'rgba(0, 0, 0, 0.4)';
424
645
  const defaultStyle = new Style({
646
+ geometry: geometry,
425
647
  // need to explicitly mention lineCap: round, so that MapfishPrint does not use butted as default
426
648
  stroke: new Stroke({
427
649
  color: dFeature.strokeColor,
@@ -576,7 +798,7 @@ export default class OlDrawing {
576
798
  addLabel(getHalfPoint(segment), dFeature.getLengthText(getDistance(segment, this.state.projection)));
577
799
  addLabel(square.getInteriorPoint(), dFeature.getAreaText(getArea(square, this.state.projection)));
578
800
  }
579
- if (dFeature.selected) {
801
+ if (dFeature.selected && this.rotateAndScale == null) {
580
802
  const vertexStyle = dFeature.getVertexStyle();
581
803
  // Add a node style to every vertex of the geometry
582
804
  vertexStyle.setGeometry(function (f) {
@@ -587,6 +809,35 @@ export default class OlDrawing {
587
809
  });
588
810
  styles.push(vertexStyle);
589
811
  }
812
+ // Draw Point/Circle for Center of Geometry if rotating/scaling
813
+ if (this.rotateAndScale != null) {
814
+ const result = calculateCenter(geometry);
815
+ const center = result.center;
816
+ if (center) {
817
+ styles.push(new Style({
818
+ geometry: new Point(center),
819
+ image: new CircleStyle({
820
+ radius: 4,
821
+ fill: new Fill({
822
+ color: '#ff3333'
823
+ })
824
+ })
825
+ }));
826
+ const coordinates = result.coordinates;
827
+ if (coordinates) {
828
+ const minRadius = result.minRadius;
829
+ const sqDistances = result.sqDistances;
830
+ const rsq = minRadius * minRadius;
831
+ const points = coordinates.filter(function (_c, index) {
832
+ return sqDistances[index] > rsq;
833
+ });
834
+ const vertexStyle = dFeature.getVertexStyle();
835
+ // Add a node style to every vertex of the geometry
836
+ vertexStyle.setGeometry(new MultiPoint(points));
837
+ styles.push(vertexStyle);
838
+ }
839
+ }
840
+ }
590
841
  return styles;
591
842
  }
592
843
  ensurePolygonIsProperlyClosed(polygon) {
@@ -45,7 +45,7 @@ export default class MapComponent extends GirafeHTMLElement {
45
45
  return uHtml `<style>
46
46
  *{font-family:Arial,sans-serif}.hidden{display:none!important}.gg-rotate90{transform:rotate(90deg)}.gg-rotate180{transform:rotate(180deg)}.gg-rotate270{transform:rotate(270deg)}img{filter:var(--svg-filter)}img.legend-image{filter:var(--svg-map-filter);background:var(--svg-legend-bkg)}div{scrollbar-width:thin}a,a:visited{color:var(--link-color)}@keyframes spin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}@keyframes spin-wait{0%{transform:rotate(0)}7%{transform:rotate(360deg)}to{transform:rotate(360deg)}}.gg-spin{animation-name:spin;animation-duration:2s;animation-timing-function:linear;animation-iteration-count:infinite}.gg-spin-wait{animation-name:spin-wait;animation-duration:10s;animation-timing-function:linear;animation-iteration-count:infinite}::-webkit-scrollbar{width:5px}::-webkit-scrollbar-thumb{background:#999}.gg-button,.gg-select,.gg-input,.gg-textarea{background-color:var(--bkg-color);color:var(--text-color);border:var(--app-standard-border);box-sizing:border-box;cursor:pointer;border-radius:3px;outline:0;margin:0;padding:0 0 0 .5rem;display:inline-block}.gg-label{background-color:var(--bkg-color);color:var(--text-color);border:none;align-items:center;margin:0;padding:0;display:flex}.gg-button,.gg-select,.gg-input,.gg-label{min-height:calc(var(--app-standard-height)/1.5)}.gg-textarea{max-height:initial;resize:vertical;height:6rem;padding:.5rem;line-height:1.3rem}.gg-input{cursor:text}.gg-checkbox{accent-color:var(--text-color);width:1.2rem}.gg-range{accent-color:var(--text-color)}.gg-button{padding:0 .5rem}.gg-button.active{border:solid 1px var(--text-color-grad2);background-color:var(--text-color-grad2);color:var(--bkg-color)}.gg-button:disabled{color:gray;cursor:not-allowed;background-color:#d3d3d3;border:none}.gg-input:disabled,.gg-select:disabled,.gg-textarea:disabled{color:gray;cursor:not-allowed;background-color:#d3d3d3}.gg-button>img{vertical-align:middle}.gg-icon-button{color:var(--text-color);cursor:pointer;background-color:#0000;border:none;flex-direction:column;justify-content:center;align-items:center;padding:0;display:flex}.gg-big,.gg-big-withtext{min-width:var(--app-standard-height);min-height:var(--app-standard-height);max-height:var(--app-standard-height)}.gg-big img,.gg-big-withtext img{width:calc(var(--app-standard-height) - 1.5rem);margin:0}.gg-big-withtext span{font-variant:small-caps;padding:0 1rem;font-size:.9rem}.gg-medium,.gg-medium-withtext{min-width:calc(var(--app-standard-height)/1.2);min-height:calc(var(--app-standard-height)/1.2);max-height:calc(var(--app-standard-height)/1.2);flex-direction:row}.gg-medium img{width:calc(var(--app-standard-height)/2.4);margin:0}.gg-medium-withtext img{width:calc(var(--app-standard-height)/2.4);margin-left:.5rem}.gg-medium-withtext span{padding:0 1rem 0 .5rem;font-size:.9rem}.gg-small,.gg-small-withtext{min-width:calc(var(--app-standard-height)/2);min-height:calc(var(--app-standard-height)/2);max-height:calc(var(--app-standard-height)/2);flex-direction:row}.gg-small img{width:calc(var(--app-standard-height)/3);margin:0}.gg-small-withtext img{width:calc(var(--app-standard-height)/3);margin-left:.5rem}.gg-small-withtext span{padding:0 .5rem 0 .3rem;font-size:.9rem}.gg-button:hover,.gg-select:hover,.gg-input:hover,.gg-textarea:hover,.gg-icon-button:hover{background-color:var(--bkg-color-grad1)}.gg-opacity{opacity:.5}.gg-opacity:hover{opacity:1;background-color:#0000}.gg-tabs{cursor:pointer;border-bottom:1px solid gray;justify-content:left;align-items:end;gap:1rem;height:2rem;margin:.5rem auto 1rem;display:flex}.gg-tab{cursor:pointer;color:var(--text-color);background:0 0;border:none;padding:.5rem}.gg-tab.active{border-bottom:solid 2px var(--text-color-grad2);font-weight:600}.girafe-button-big,.girafe-button-large,.girafe-button-small,.girafe-button-tiny{color:var(--text-color);background-color:#0000;border:none;flex-direction:column;display:flex}.girafe-button-big:hover,.girafe-button-large:hover,.girafe-button-small:hover,.girafe-button-tiny:hover{background-color:var(--bkg-color-grad1);cursor:pointer}.girafe-button-big.dark,.girafe-button-large.dark,.girafe-button-small.dark,.girafe-button-tiny.dark{background-color:var(--bkg-color);filter:invert()}.girafe-button-big{width:var(--app-standard-height);height:var(--app-standard-height);align-items:center;padding:1rem}.girafe-button-big img{overflow:hidden}.girafe-button-large{flex-direction:row}.girafe-button-large img{height:2rem;margin:.3rem}.girafe-button-large span{height:2rem;margin:.3rem;line-height:2rem}.girafe-button-small{min-width:calc(var(--app-standard-height)/2);height:calc(var(--app-standard-height)/2);align-items:center;padding:.5rem}.girafe-button-small img{overflow:hidden}.girafe-button-small span{text-align:left;text-overflow:ellipsis;width:100%;overflow:hidden}.girafe-button-tiny{align-items:center;width:1rem;height:1rem;padding:0}.girafe-button-tiny img{overflow:hidden}
47
47
  </style><style>
48
- #container,#ol-map{background-color:var(--bkg-color);width:100%;height:100%;position:relative}#ol-map.darkmap canvas{filter:invert()hue-rotate(180deg)}.hidden{display:none}.center{text-align:center;margin:10px;position:absolute;top:50%;left:50%;transform:translate(-50%,-50%)}.loading span{color:var(--text-color);margin-top:1rem;font-weight:600;display:block}.loading span.quote{font-style:italic;font-weight:300}#cs-map{background-color:var(--bkg-color);border-left:3px solid #444;height:100%;display:none;position:absolute;overflow:hidden}.ol-control{background-color:#0000;border:none;box-shadow:0 1px 4px #0000004d;right:2rem!important;left:unset!important}.ol-zoom{top:2rem!important}.ol-rotate{top:9.3rem!important}.ol-location{top:7rem!important}.img-location{width:55%;height:auto}.ol-zoom-in,.ol-zoom-out,.btn-location,.ol-rotate-reset{cursor:pointer;background-color:#fff;width:2rem!important;height:2rem!important;font-size:1.2rem!important}.ol-scale-line{bottom:.5rem!important;right:1rem!important;left:unset!important}.ol-popup{background-color:var(--bkg-color);color:var(--text-color);box-shadow:0 1px 4px var(--bx-shdw);border:1px solid var(--text-color-grad1);border-radius:10px;min-width:140px;padding:15px;position:absolute;bottom:12px;left:-50px}.ol-popup:after,.ol-popup:before{content:" ";pointer-events:none;border:solid #0000;width:0;height:0;position:absolute;top:100%}.ol-popup:after{border-top-color:var(--bkg-color);border-width:10px;margin-left:-10px;left:48px}.ol-popup:before{border-top-color:var(--text-color-grad1);border-width:11px;margin-left:-11px;left:48px}.ol-popup-closer{background-color:var(--bkg-color);color:var(--text-color);cursor:pointer;text-decoration:none;position:absolute;top:2px;right:8px}.ol-popup-closer:after{content:"✖"}#swiper{width:99%;height:0;display:none;position:absolute;top:50%;left:.5%}input[type=range]{-webkit-appearance:none;width:100%}input[type=range]::-webkit-slider-runnable-track{height:0}input[type=range]::-webkit-slider-thumb{-webkit-appearance:none;background:var(--bkg-color);cursor:ew-resize;border:2px solid #444;width:6px;height:0;margin-top:-1000px;padding-top:1000px;padding-bottom:1000px}input[type=range]::-moz-range-thumb{background:var(--bkg-color);cursor:ew-resize;border:2px solid #444;width:3px;height:0;padding-top:1000px;padding-bottom:1000px}.close-swiper{background-color:var(--ol-background-color);width:2rem;height:2rem;color:var(--ol-subtle-foreground-color);cursor:pointer;border-width:1px;border-radius:4px;display:none;position:absolute;top:0;transform:translate(-3px)}.close-swiper:hover,.close-swiper:focus{outline:1px solid var(--ol-subtle-foreground-color);color:var(--ol-foreground-color)}.ol-viewport .tooltip{color:#fff;opacity:.7;white-space:nowrap;background:#00000080;border-radius:0;padding:.32rem .62rem;position:relative}@media screen and (hover:none){.ol-zoom,.ol-rotate,.ol-location{display:none!important}}.contextmenu{background-color:var(--bkg-color);color:var(--text-color);border:1px solid #ccc;box-shadow:1px 3px 4px #0006;& .hidden{display:none}& .menu-entry{cursor:pointer;padding:10px}}
48
+ #container,#ol-map{background-color:var(--bkg-color);width:100%;height:100%;position:relative}#ol-map.darkmap canvas{filter:invert()hue-rotate(180deg)}.hidden{display:none}.center{text-align:center;margin:10px;position:absolute;top:50%;left:50%;transform:translate(-50%,-50%)}.loading span{color:var(--text-color);margin-top:1rem;font-weight:600;display:block}.loading span.quote{font-style:italic;font-weight:300}#cs-map{background-color:var(--bkg-color);border-left:3px solid #444;height:100%;display:none;position:absolute;overflow:hidden}.ol-control{background-color:#0000;border:none;box-shadow:0 1px 4px #0000004d;right:2rem!important;left:unset!important}.ol-zoom{top:2rem!important}.ol-rotate{top:9.3rem!important}.ol-location{top:7rem!important}.img-location{width:55%;height:auto}.ol-zoom-in,.ol-zoom-out,.btn-location,.ol-rotate-reset{cursor:pointer;background-color:#fff;width:2rem!important;height:2rem!important;font-size:1.2rem!important}.ol-scale-line{bottom:.5rem!important;right:1rem!important;left:unset!important}.ol-popup{background-color:var(--bkg-color);color:var(--text-color);box-shadow:0 1px 4px var(--bx-shdw);border:1px solid var(--text-color-grad1);border-radius:10px;min-width:140px;padding:15px;position:absolute;bottom:12px;left:-50px}.ol-popup:after,.ol-popup:before{content:" ";pointer-events:none;border:solid #0000;width:0;height:0;position:absolute;top:100%}.ol-popup:after{border-top-color:var(--bkg-color);border-width:10px;margin-left:-10px;left:48px}.ol-popup:before{border-top-color:var(--text-color-grad1);border-width:11px;margin-left:-11px;left:48px}.ol-popup-closer{background-color:var(--bkg-color);color:var(--text-color);cursor:pointer;text-decoration:none;position:absolute;top:2px;right:8px}.ol-popup-closer:after{content:"✖"}#swiper{width:99%;height:0;display:none;position:absolute;top:50%;left:.5%}input[type=range]{-webkit-appearance:none;width:100%}input[type=range]::-webkit-slider-runnable-track{height:0}input[type=range]::-webkit-slider-thumb{-webkit-appearance:none;background:var(--bkg-color);cursor:ew-resize;border:2px solid #444;width:6px;height:0;margin-top:-1000px;padding-top:1000px;padding-bottom:1000px}input[type=range]::-moz-range-thumb{background:var(--bkg-color);cursor:ew-resize;border:2px solid #444;width:3px;height:0;padding-top:1000px;padding-bottom:1000px}.close-swiper{background-color:var(--ol-background-color);width:2rem;height:2rem;color:var(--ol-subtle-foreground-color);cursor:pointer;border-width:1px;border-radius:4px;display:none;position:absolute;top:0;transform:translate(-3px)}.close-swiper:hover,.close-swiper:focus{outline:1px solid var(--ol-subtle-foreground-color);color:var(--ol-foreground-color)}.ol-viewport .tooltip{color:#fff;opacity:.7;white-space:nowrap;background:#00000080;border-radius:0;padding:.32rem .62rem;position:relative}@media screen and (hover:none){.ol-zoom,.ol-rotate,.ol-location{display:none!important}}.contextmenu{background-color:var(--bkg-color);color:var(--text-color);border:1px solid #ccc;box-shadow:1px 3px 4px #0006;& .hidden{display:none}& .menu-entry{cursor:pointer;padding:10px}& .menu-entry:hover{background-color:var(--bkg-color-grad1)}& .menu-entry[aria-disabled=true]{pointer-events:none;color:var(--text-color-grad1)}}
49
49
  </style>
50
50
  <link rel="stylesheet" href="lib/ol/ol.css"><div id="container"><div id="ol-map"></div><div class="ol-location ol-unselectable ol-control"><button onclick="${() => this.locateUser()}" class="btn-location"><img class="img-location" alt="location-icon" src="icons/adjust.svg"></button></div><div id="cs-map"><div class="${this.loading ? 'loading center' : 'loading hidden'}"><img alt="loading-icon" src="icons/loading.svg" class="gg-spin"> <span i18n="Loading cesium...">Loading cesium...</span> <span class="quote" i18n="cesium-loading-quote">Please be patient, like a giraffe reaching for the tastiest leaves.</span></div></div><input id="swiper" type="range" min="0" max="1000" step="1"> <button tip="Hide Swiper" id="close-swiper" class="close-swiper">×</button></div>`;
51
51
  };
@@ -1,8 +1,14 @@
1
1
  import { Coordinate } from 'ol/coordinate';
2
2
  import IGirafeContext from '../../../tools/context/icontext';
3
+ export declare enum EntryInteractionType {
4
+ ENABLED = 0,
5
+ DISABLED = 1,
6
+ NOT_SHOWN = 2
7
+ }
3
8
  export type MenuEntry = {
4
9
  entry: string;
5
10
  callback: (e: MouseEvent, mapCoordinate: Coordinate) => void;
11
+ prepare?: (e: MouseEvent, mapCoordinate: Coordinate) => EntryInteractionType;
6
12
  };
7
13
  /**
8
14
  * Show a simple context menu when clicking on the map
@@ -16,6 +22,8 @@ export declare class ContextMenu {
16
22
  private readonly openEventListener;
17
23
  private readonly closeEventListener;
18
24
  private readonly context;
25
+ private readonly menuEntries;
26
+ private menuEntryDivs;
19
27
  private get map();
20
28
  constructor(context: IGirafeContext, menuEntries?: MenuEntry[], isExclusive?: boolean, openCondition?: (evt: PointerEvent | MouseEvent, mapCoordinate: Coordinate) => boolean);
21
29
  get visible(): boolean;
@@ -24,7 +32,8 @@ export declare class ContextMenu {
24
32
  private enable;
25
33
  private disable;
26
34
  private handleContextmenuEvent;
27
- updateMenuEntries(menuEntries: MenuEntry[]): void;
35
+ updateMenuEntries(): void;
36
+ prepareMenuEntries(evt: PointerEvent | MouseEvent, mapCoordinate: Coordinate): void;
28
37
  openMenu(mapCoordinates: Coordinate): void;
29
38
  closeMenu(): void;
30
39
  onClickMenuEntry(evt: PointerEvent | MouseEvent, callback: (e: PointerEvent | MouseEvent, mapCoordinate: Coordinate) => void): void;
@@ -1,5 +1,11 @@
1
1
  import { Overlay } from 'ol';
2
2
  import { v4 as uuidv4 } from 'uuid';
3
+ export var EntryInteractionType;
4
+ (function (EntryInteractionType) {
5
+ EntryInteractionType[EntryInteractionType["ENABLED"] = 0] = "ENABLED";
6
+ EntryInteractionType[EntryInteractionType["DISABLED"] = 1] = "DISABLED";
7
+ EntryInteractionType[EntryInteractionType["NOT_SHOWN"] = 2] = "NOT_SHOWN";
8
+ })(EntryInteractionType || (EntryInteractionType = {}));
3
9
  /**
4
10
  * Show a simple context menu when clicking on the map
5
11
  */
@@ -12,17 +18,20 @@ export class ContextMenu {
12
18
  openEventListener;
13
19
  closeEventListener;
14
20
  context;
21
+ menuEntries;
22
+ menuEntryDivs = {};
15
23
  get map() {
16
24
  return this.context.mapManager.getMap();
17
25
  }
18
26
  constructor(context, menuEntries = [], isExclusive = false, openCondition = () => true) {
19
27
  this.name = `contextmenu-${uuidv4()}`;
20
28
  this.context = context;
29
+ this.menuEntries = menuEntries;
21
30
  this.isExclusive = isExclusive;
22
31
  this.container = document.createElement('div');
23
32
  this.container.classList.add('contextmenu');
24
33
  this.container.classList.add('hidden');
25
- this.updateMenuEntries(menuEntries);
34
+ this.updateMenuEntries();
26
35
  this.overlay = new Overlay({
27
36
  element: this.container,
28
37
  position: undefined
@@ -84,21 +93,44 @@ export class ContextMenu {
84
93
  evt.offsetY / window.devicePixelRatio
85
94
  ]);
86
95
  if (openCondition(evt, mapCoordinate)) {
96
+ this.prepareMenuEntries(evt, mapCoordinate);
87
97
  this.openMenu(mapCoordinate);
88
98
  }
89
99
  else {
90
100
  evt.stopPropagation();
91
101
  }
92
102
  }
93
- updateMenuEntries(menuEntries) {
103
+ updateMenuEntries() {
94
104
  this.container.replaceChildren('');
95
- menuEntries.forEach((entry) => {
105
+ this.menuEntryDivs = {};
106
+ for (const entry of this.menuEntries) {
96
107
  const div = document.createElement('div');
97
108
  div.classList.add('menu-entry');
98
- div.innerHTML = entry.entry;
109
+ div.innerHTML = this.context.i18nManager.getTranslation(entry.entry);
99
110
  div.addEventListener('click', (evt) => this.onClickMenuEntry(evt, entry.callback));
100
111
  this.container.appendChild(div);
101
- });
112
+ this.menuEntryDivs[entry.entry] = div;
113
+ }
114
+ }
115
+ prepareMenuEntries(evt, mapCoordinate) {
116
+ for (const entry of this.menuEntries) {
117
+ const interactionType = entry.prepare?.(evt, mapCoordinate) ?? EntryInteractionType.ENABLED;
118
+ const menuEntryDiv = this.menuEntryDivs[entry.entry];
119
+ switch (interactionType) {
120
+ case EntryInteractionType.ENABLED:
121
+ menuEntryDiv.ariaDisabled = 'false';
122
+ menuEntryDiv.style.display = 'block';
123
+ break;
124
+ case EntryInteractionType.DISABLED:
125
+ menuEntryDiv.ariaDisabled = 'true';
126
+ menuEntryDiv.style.display = 'block';
127
+ break;
128
+ case EntryInteractionType.NOT_SHOWN:
129
+ menuEntryDiv.ariaDisabled = 'true';
130
+ menuEntryDiv.style.display = 'none';
131
+ break;
132
+ }
133
+ }
102
134
  }
103
135
  openMenu(mapCoordinates) {
104
136
  if (!this.container.parentNode) {
@@ -19,9 +19,9 @@ class ModalsComponent extends GirafeHTMLElement {
19
19
  constructor() {
20
20
  super('native-modals');
21
21
  }
22
- initBox(type, message, title, placeholder) {
22
+ initBox(type, message, messageHandler, title, placeholder) {
23
23
  this.boxType = type;
24
- this.boxMessage = this.context.i18nManager.getTranslation(message);
24
+ this.boxMessage = messageHandler(this.context.i18nManager.getTranslation(message));
25
25
  if (title) {
26
26
  this.boxTitle = this.context.i18nManager.getTranslation(title);
27
27
  }
@@ -40,13 +40,13 @@ class ModalsComponent extends GirafeHTMLElement {
40
40
  this.refreshRender();
41
41
  }
42
42
  async promptBox(message, title, placeholder) {
43
- this.initBox('prompt', message, title, placeholder);
43
+ this.initBox('prompt', message, (message) => message, title, placeholder);
44
44
  return new Promise((resolve) => {
45
45
  this.resolvePrompt = resolve;
46
46
  });
47
47
  }
48
- async alertConfirmBox(type, message, title) {
49
- this.initBox(type, message, title);
48
+ async alertConfirmBox(type, message, title, messageHandler) {
49
+ this.initBox(type, message, messageHandler, title);
50
50
  return new Promise((resolve) => {
51
51
  this.resolveAlertConfirm = resolve;
52
52
  });
@@ -75,8 +75,9 @@ class ModalsComponent extends GirafeHTMLElement {
75
75
  }
76
76
  connectedCallback() {
77
77
  super.connectedCallback();
78
- window.gConfirm = (message, title) => this.alertConfirmBox('confirm', message, title);
79
- window.gAlert = (message, title) => this.alertConfirmBox('alert', message, title);
78
+ const defaultMessageHandler = (translatedMessage) => translatedMessage;
79
+ window.gConfirm = (message, title, messageHandler) => this.alertConfirmBox('confirm', message, title, messageHandler ?? defaultMessageHandler);
80
+ window.gAlert = (message, title) => this.alertConfirmBox('alert', message, title ?? '', defaultMessageHandler);
80
81
  window.gPrompt = (message, title, placeholder) => this.promptBox(message, title, placeholder);
81
82
  this.render();
82
83
  super.girafeTranslate();
package/main.tools.d.ts CHANGED
@@ -11,7 +11,7 @@ declare global {
11
11
  CESIUM_BASE_URL: string;
12
12
  Cesium: unknown;
13
13
  cordova: unknown;
14
- gConfirm(message: string, title?: string): Promise<boolean>;
14
+ gConfirm(message: string, title: string, messageHandler?: (translatedMessage: string) => string): Promise<boolean>;
15
15
  gAlert(message: string, title?: string): Promise<boolean>;
16
16
  gPrompt(message: string, title?: string, placeholder?: string): Promise<string | false>;
17
17
  gOpenWindow(title: string, url: string, width?: string | number, height?: string | number, top?: string | number, left?: string | number): void;
package/package.json CHANGED
@@ -5,7 +5,7 @@
5
5
  "name": "GeoGirafe PSC",
6
6
  "url": "https://doc.geomapfish.dev"
7
7
  },
8
- "version": "1.0.2149179221",
8
+ "version": "1.0.2152254142",
9
9
  "type": "module",
10
10
  "engines": {
11
11
  "node": ">=20.19.0"
@@ -1 +1 @@
1
- {"version":"1.0.2149179221", "build":"2149179221", "date":"10/11/2025"}
1
+ {"version":"1.0.2152254142", "build":"2152254142", "date":"11/11/2025"}