@geogirafe/lib-geoportal 1.2.0-dev.2736532702 → 1.2.0-dev.2751114005

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.
Files changed (42) hide show
  1. package/LICENSE +1 -0
  2. package/assets/i18n/de.json +3 -0
  3. package/assets/i18n/en.json +3 -0
  4. package/assets/i18n/fr.json +3 -0
  5. package/assets/i18n/it.json +3 -0
  6. package/assets/icons/share-native.svg +1 -0
  7. package/assets/icons/text-font.svg +1 -0
  8. package/components/drawing/component-mobile.js +1 -1
  9. package/components/drawing/component.d.ts +8 -1
  10. package/components/drawing/component.js +61 -6
  11. package/components/main.d.ts +1 -0
  12. package/components/main.js +1 -0
  13. package/components/menu-buttons-mobile/MenuMobile3dButton.js +1 -1
  14. package/components/menu-buttons-mobile/MenuMobileDrawingButton.js +1 -1
  15. package/components/menu-buttons-mobile/MenuMobileOfflineButton.js +1 -1
  16. package/components/menu-buttons-mobile/MenuMobileShareButton.d.ts +11 -0
  17. package/components/menu-buttons-mobile/MenuMobileShareButton.js +32 -0
  18. package/components/menu-buttons-mobile/style.css +3 -0
  19. package/components/menu-mobile/component.js +1 -1
  20. package/components/menu-mobile/style.css +4 -0
  21. package/components/share/component.js +4 -4
  22. package/components/share-mobile/component.d.ts +28 -0
  23. package/components/share-mobile/component.js +121 -0
  24. package/components/share-mobile/style.css +56 -0
  25. package/components/treeview/treeviewitem/component.js +9 -2
  26. package/models/layers/layerdrawing.d.ts +1 -0
  27. package/models/layers/layerdrawing.js +2 -1
  28. package/package.json +1 -1
  29. package/templates/mobile.html +2 -0
  30. package/templates/public/about.json +1 -1
  31. package/tools/app/geogirafeapp-mobile.js +4 -0
  32. package/tools/drawing/cesiumDrawing.d.ts +7 -0
  33. package/tools/drawing/cesiumDrawing.js +24 -1
  34. package/tools/drawing/drawingFeature.d.ts +3 -2
  35. package/tools/drawing/drawingFeature.js +5 -2
  36. package/tools/drawing/olDrawing.d.ts +41 -0
  37. package/tools/drawing/olDrawing.js +170 -15
  38. package/tools/state/graphicalInterface.d.ts +1 -1
  39. /package/{components/share/images → assets/icons}/facebook.svg +0 -0
  40. /package/{components/share/images → assets/icons}/linkedin.svg +0 -0
  41. /package/{components/share/images/mail.svg → assets/icons/share-mail.svg} +0 -0
  42. /package/{components/share/images → assets/icons}/twitter.svg +0 -0
@@ -8,7 +8,8 @@ import { Draw, Modify, Snap, Translate } from 'ol/interaction.js';
8
8
  import VectorSource from 'ol/source/Vector.js';
9
9
  import VectorLayer from 'ol/layer/Vector.js';
10
10
  import { getPointResolution } from 'ol/proj.js';
11
- import { always, never, primaryAction } from 'ol/events/condition.js';
11
+ import { always, never, platformModifierKeyOnly, primaryAction } from 'ol/events/condition.js';
12
+ import { unByKey } from 'ol/Observable.js';
12
13
  import { ensurePolygonIsProperlyClosed, getAreaOfPolygon, getDistance, getHalfPoint, getLabelStyle, getLengthAsMetricText, getRadiusDataForCircle, unByKeyAll } from '../utils/olutils.js';
13
14
  import { ContextMenu, EntryInteractionType } from '../../components/map/tools/contextmenu.js';
14
15
  import { formatCoordinates } from '../geometrytools.js';
@@ -64,12 +65,16 @@ export default class OlDrawing {
64
65
  fixedSquareSide = 0;
65
66
  fixedRectangleWidth = 0;
66
67
  fixedRectangleHeight = 0;
68
+ drawingText = '';
67
69
  drawingSource;
68
70
  drawingLayer;
69
71
  lastClosestFeature = null;
70
72
  translate = null;
71
73
  transform = null;
72
74
  modifyFeatureChangeListeners = [];
75
+ hoverListener = null;
76
+ selectListener = null;
77
+ hoveredTextFeatureId = null;
73
78
  defaultStyle;
74
79
  updateGeometryInState = (olFeature) => {
75
80
  const idx = this.drawingState.features.findIndex((f) => f.id === olFeature.getId());
@@ -324,21 +329,78 @@ export default class OlDrawing {
324
329
  if (!this.editContextMenu) {
325
330
  this.addEditContextMenu();
326
331
  }
332
+ this.addHoverListener();
327
333
  // Always recreate snap interaction to get snapping behavior on latest features
328
334
  this.addSnapInteraction();
329
335
  }
330
336
  removeEditInteractions() {
331
337
  this.removeModifyInteraction();
332
338
  this.removeEditContextMenu();
339
+ this.removeHoverListener();
333
340
  if (!this.draw) {
334
341
  this.removeSnapInteraction();
335
342
  }
336
343
  }
344
+ addHoverListener() {
345
+ this.hoverListener ??= this.map.olMap.on('pointermove', (e) => this.updateHoveredTextFeature(e));
346
+ }
347
+ removeHoverListener() {
348
+ if (this.hoverListener) {
349
+ unByKey(this.hoverListener);
350
+ this.hoverListener = null;
351
+ }
352
+ this.setHoveredTextFeature(null);
353
+ }
354
+ addSelectListener() {
355
+ this.selectListener ??= this.map.olMap.on('singleclick', (e) => this.selectClickedFeature(e));
356
+ }
357
+ removeSelectListener() {
358
+ if (this.selectListener) {
359
+ unByKey(this.selectListener);
360
+ this.selectListener = null;
361
+ }
362
+ }
363
+ /**
364
+ * Lets the user select the shapes to edit directly in the map, as an alternative to the drawing list:
365
+ * a click selects the shape under the cursor exclusively, a click with the platform modifier key (ctrl / meta)
366
+ * adds the shape to the current selection or removes it from it. A click outside of any shape clears the selection.
367
+ */
368
+ selectClickedFeature(e) {
369
+ // While drawing, moving or transforming a shape, the click belongs to the interaction in progress
370
+ if (this.draw || this.translate || this.transform || !this.canExecute('map.select')) {
371
+ return;
372
+ }
373
+ const clickedFeatureId = this.findFeatureIdAtPixel(e.pixel);
374
+ if (platformModifierKeyOnly(e)) {
375
+ const clickedFeature = this.drawingState.features.find((f) => f.id === clickedFeatureId);
376
+ if (clickedFeature) {
377
+ this.setFeatureSelected(clickedFeature, !clickedFeature.selected);
378
+ }
379
+ }
380
+ else {
381
+ this.drawingState.features.forEach((f) => this.setFeatureSelected(f, f.id === clickedFeatureId));
382
+ }
383
+ }
384
+ /**
385
+ * Only writes to the state if the selection of the shape really changed, so that the map and the drawing
386
+ * panel are not refreshed for the shapes that are not concerned by the click.
387
+ */
388
+ setFeatureSelected(dFeature, selected) {
389
+ if (dFeature.selected !== selected) {
390
+ dFeature.selected = selected;
391
+ }
392
+ }
393
+ findFeatureIdAtPixel(pixel) {
394
+ const clickedFeature = this.map.olMap.forEachFeatureAtPixel(pixel, (feature, layer) => (feature instanceof Feature && layer === this.drawingLayer ? feature : undefined), {
395
+ hitTolerance: this.map.pixelTolerance
396
+ });
397
+ return clickedFeature ? (clickedFeature.getId() ?? null) : null;
398
+ }
337
399
  prepareMenuEntriesForVertex = (_e, mapCoordinate) => {
338
400
  if (!this.hasEditableVertexAtCoordinate(mapCoordinate)) {
339
401
  return EntryInteractionType.NOT_SHOWN;
340
402
  }
341
- if (this.isSquareOrRectangle(this.getDrawingShapeType(this.lastClosestFeature))) {
403
+ if (this.isSquareRectangleOrText(this.getDrawingShapeType(this.lastClosestFeature))) {
342
404
  return EntryInteractionType.NOT_SHOWN;
343
405
  }
344
406
  return EntryInteractionType.ENABLED;
@@ -476,6 +538,7 @@ export default class OlDrawing {
476
538
  }
477
539
  const olFeature = e.feature;
478
540
  const dFeature = new DrawingFeature(this.currentShape, this.drawingState, this.config.drawing);
541
+ this.applyDrawingText(dFeature);
479
542
  olFeature.setId(dFeature.id);
480
543
  dFeature.geojson = DrawingFeature.geojsonFromOlFeature(olFeature, dFeature.type);
481
544
  dFeature.addToState();
@@ -507,6 +570,18 @@ export default class OlDrawing {
507
570
  setFixedRectangleHeight(height) {
508
571
  this.fixedRectangleHeight = Number.isNaN(height) ? 0 : height;
509
572
  }
573
+ setDrawingText(text) {
574
+ this.drawingText = text;
575
+ }
576
+ /**
577
+ * A text shape displays the text entered in the tool parameters instead of its generated default name.
578
+ * If no text has been entered, the default name is kept so that the shape stays visible on the map.
579
+ */
580
+ applyDrawingText(dFeature) {
581
+ if (dFeature.type === DrawingShape.Text && this.drawingText.trim().length > 0) {
582
+ dFeature.name = this.drawingText;
583
+ }
584
+ }
510
585
  createLineStringFixedLength(coordinates, geom) {
511
586
  this.fixLastLength(this.fixedLineLength, coordinates);
512
587
  geom = geom ?? new LineString(coordinates);
@@ -582,8 +657,6 @@ export default class OlDrawing {
582
657
  }
583
658
  addDrawInteraction(tool) {
584
659
  this.removeDrawInteraction();
585
- // Block feature selection while drawing by registering 'map.select' exclusively
586
- this.context.userInteractionManager.registerListener('map.select', true, this.stateLocation);
587
660
  this.currentShape = tool;
588
661
  let geomFunction = undefined;
589
662
  let olTool;
@@ -617,6 +690,9 @@ export default class OlDrawing {
617
690
  case DrawingShape.FreehandPolygon:
618
691
  olTool = 'Polygon';
619
692
  break;
693
+ case DrawingShape.Text:
694
+ olTool = 'Point';
695
+ break;
620
696
  }
621
697
  this.draw = new Draw({
622
698
  source: this.drawingSource,
@@ -627,9 +703,17 @@ export default class OlDrawing {
627
703
  // Default condition for ol drawing is noModifierKeys(e)
628
704
  // canExecute: If another tool is exclusively drawing, this interaction will be prevented from reacting
629
705
  condition: (e) => primaryAction(e) && this.canExecute('map.draw'),
630
- style: (f) => this.getStyle(new DrawingFeature(tool, this.drawingState, this.config.drawing), f)
706
+ style: (f) => {
707
+ const sketchFeature = new DrawingFeature(tool, this.drawingState, this.config.drawing);
708
+ this.applyDrawingText(sketchFeature);
709
+ return this.getStyle(sketchFeature, f);
710
+ }
711
+ });
712
+ this.draw.on('drawstart', () => {
713
+ this.disableContextMenu();
631
714
  });
632
715
  this.draw.on('drawend', () => {
716
+ this.enableContextMenu();
633
717
  this.draw?.removeLastPoint();
634
718
  this.draw?.finishDrawing();
635
719
  });
@@ -663,17 +747,26 @@ export default class OlDrawing {
663
747
  lineDash: getLineStroke(dFeature.lineStroke, dFeature.strokeWidth)
664
748
  }),
665
749
  fill: new Fill({ color: dFeature.fillColor }),
666
- image: new Circle({
667
- radius: dFeature.strokeWidth, // Points are using default stroke parameters
668
- fill: new Fill({ color: dFeature.strokeColor })
669
- }),
750
+ image: dFeature.type === DrawingShape.Text
751
+ ? // A text shape is only made of its text, its anchor point is not drawn
752
+ undefined
753
+ : new Circle({
754
+ radius: dFeature.strokeWidth, // Points are using default stroke parameters
755
+ fill: new Fill({ color: dFeature.strokeColor })
756
+ }),
670
757
  text: new Text({
671
758
  text: dFeature.displayName ? dFeature.name : '',
672
759
  font: nameFont,
673
760
  textBaseline: 'middle',
674
761
  /*offsetY: dFeature.type == DrawingShape.Point ? 2 * dFeature.nameFontSize : 1.2 * dFeature.nameFontSize,*/
675
762
  fill: new Fill({ color: dFeature.nameColor }),
676
- overflow: true
763
+ overflow: true,
764
+ stroke: dFeature.type == DrawingShape.Text
765
+ ? new Stroke({
766
+ color: '#ffffff',
767
+ width: 8
768
+ })
769
+ : undefined
677
770
  })
678
771
  });
679
772
  const labelStyle = new Style({
@@ -768,7 +861,10 @@ export default class OlDrawing {
768
861
  }
769
862
  }
770
863
  }
771
- if (dFeature.type == DrawingShape.Point || geometry.getType() === 'Point') {
864
+ if (dFeature.type == DrawingShape.Text) {
865
+ // A text shape displays only its text, there is no measure to add
866
+ }
867
+ else if (dFeature.type == DrawingShape.Point || geometry.getType() === 'Point') {
772
868
  addLabel(geometry, dFeature.getCoordText(geometry.getCoordinates()));
773
869
  }
774
870
  else if (dFeature.type == DrawingShape.Polyline) {
@@ -792,7 +888,7 @@ export default class OlDrawing {
792
888
  appendText(getLengthAsMetricText(lineString.getLength()));
793
889
  }
794
890
  }
795
- else if (dFeature.type == DrawingShape.Disk) {
891
+ else if (dFeature.type == DrawingShape.Disk && geometry instanceof CircleGeom) {
796
892
  if (dFeature.measureInformation !== 'none') {
797
893
  const radiusDataForCircle = getRadiusDataForCircle(geometry, this.state.projection, defaultStyle, new Stroke({ color: measureColor, width: 2 }));
798
894
  styles.push(radiusDataForCircle.style);
@@ -830,7 +926,7 @@ export default class OlDrawing {
830
926
  }
831
927
  addLabel(square.getInteriorPoint(), dFeature.getAreaText(getAreaOfPolygon(square, this.state.projection)));
832
928
  }
833
- if (dFeature.selected && this.transform == null) {
929
+ if (this.shouldRenderVertices(dFeature)) {
834
930
  const vertexStyle = dFeature.getVertexStyle();
835
931
  // Add a node style to every vertex of the geometry
836
932
  vertexStyle.setGeometry(function (f) {
@@ -843,13 +939,21 @@ export default class OlDrawing {
843
939
  }
844
940
  return styles;
845
941
  }
942
+ /**
943
+ * Vertices are rendered on the shapes selected for editing. A text shape is an exception: as its geometry is
944
+ * a single anchor point hidden behind the text, its vertex is only revealed while the cursor hovers the text.
945
+ */
946
+ shouldRenderVertices(dFeature) {
947
+ if (!dFeature.selected || this.transform != null) {
948
+ return false;
949
+ }
950
+ return dFeature.type !== DrawingShape.Text || dFeature.id === this.hoveredTextFeatureId;
951
+ }
846
952
  removeDrawInteraction() {
847
953
  if (this.draw) {
848
954
  this.map.olMap.removeInteraction(this.draw);
849
955
  this.draw = null;
850
956
  }
851
- // Reactivate feature selection by unregistering 'map.select'
852
- this.context.userInteractionManager.unregisterListener('map.select', this.stateLocation);
853
957
  }
854
958
  removeModifyInteraction() {
855
959
  this.clearModifyFeatureChangeListeners();
@@ -867,10 +971,50 @@ export default class OlDrawing {
867
971
  isSquareOrRectangle(shapeType) {
868
972
  return shapeType === DrawingShape.Square || shapeType === DrawingShape.Rectangle;
869
973
  }
974
+ isSquareRectangleOrText(shapeType) {
975
+ return shapeType === DrawingShape.Square || shapeType === DrawingShape.Rectangle || shapeType === DrawingShape.Text;
976
+ }
870
977
  shouldAllowVertexInsertionAtEvent(e) {
871
978
  const hoveredFeature = this.getHoveredModifiableFeatureAtPixel(e.pixel);
872
979
  return shouldAllowVertexInsertionForShape(this.getDrawingShapeType(hoveredFeature));
873
980
  }
981
+ /**
982
+ * A text shape is only made of its text: its anchor point is hidden behind it. To let the user know where to
983
+ * grab the shape, the anchor vertex is revealed as long as the cursor hovers the text.
984
+ */
985
+ updateHoveredTextFeature(e) {
986
+ if (e.dragging) {
987
+ return;
988
+ }
989
+ // Only the selected shapes display their vertices, so there is nothing to look for otherwise
990
+ const hasSelectedText = this.drawingState.features.some((f) => f.selected && f.type === DrawingShape.Text);
991
+ this.setHoveredTextFeature(hasSelectedText ? this.findHoveredTextFeatureIdAtPixel(e.pixel) : null);
992
+ }
993
+ setHoveredTextFeature(featureId) {
994
+ if (featureId === this.hoveredTextFeatureId) {
995
+ return;
996
+ }
997
+ const previousFeatureId = this.hoveredTextFeatureId;
998
+ this.hoveredTextFeatureId = featureId;
999
+ // Redraw the shapes whose hovered state changed, so that their vertex appears or disappears
1000
+ for (const id of [previousFeatureId, featureId]) {
1001
+ if (id) {
1002
+ this.getOlFeatureFromDrawingSource(id)?.changed();
1003
+ }
1004
+ }
1005
+ }
1006
+ findHoveredTextFeatureIdAtPixel(pixel) {
1007
+ // The text of a shape takes part in the hit detection, so hovering anywhere on it is enough
1008
+ const hoveredFeature = this.map.olMap.forEachFeatureAtPixel(pixel, (feature, layer) => {
1009
+ if (!(feature instanceof Feature) || layer !== this.drawingLayer) {
1010
+ return undefined;
1011
+ }
1012
+ return this.getDrawingShapeType(feature) === DrawingShape.Text ? feature : undefined;
1013
+ }, {
1014
+ hitTolerance: this.map.pixelTolerance
1015
+ });
1016
+ return hoveredFeature instanceof Feature ? (hoveredFeature.getId() ?? null) : null;
1017
+ }
874
1018
  getHoveredModifiableFeatureAtPixel(pixel) {
875
1019
  const modifiableFeatureSet = new Set(this.modifiableFeatures.getArray());
876
1020
  const hoveredFeature = this.map.olMap.forEachFeatureAtPixel(pixel, (feature, layer) => {
@@ -941,17 +1085,28 @@ export default class OlDrawing {
941
1085
  this.context.userInteractionManager.registerListener('map.draw', true, this.stateLocation);
942
1086
  this.context.userInteractionManager.registerListener('map.modify', true, this.stateLocation);
943
1087
  this.context.userInteractionManager.registerListener('map.snap', true, this.stateLocation);
1088
+ // Clicking on the map selects the drawn shapes, so feature selection is taken over from the other tools
1089
+ this.context.userInteractionManager.registerListener('map.select', true, this.stateLocation);
1090
+ this.addSelectListener();
944
1091
  }
945
1092
  unregisterInteractions() {
946
1093
  this.removeDrawInteraction();
947
1094
  this.removeEditInteractions();
1095
+ this.removeSelectListener();
948
1096
  this.context.userInteractionManager.unregisterListener('map.draw', this.stateLocation);
949
1097
  this.context.userInteractionManager.unregisterListener('map.modify', this.stateLocation);
950
1098
  this.context.userInteractionManager.unregisterListener('map.snap', this.stateLocation);
1099
+ this.context.userInteractionManager.unregisterListener('map.select', this.stateLocation);
951
1100
  }
952
1101
  canExecute(event) {
953
1102
  return this.context.userInteractionManager.canListenerExecute(event, this.stateLocation);
954
1103
  }
1104
+ disableContextMenu() {
1105
+ this.context.userInteractionManager.registerListener('map.contextmenu', true, this.stateLocation);
1106
+ }
1107
+ enableContextMenu() {
1108
+ this.context.userInteractionManager.unregisterListener('map.contextmenu', this.stateLocation);
1109
+ }
955
1110
  fixLastLength(length, coordinates, scale = 1) {
956
1111
  const coord = coordinates;
957
1112
  if (coord.length > 1 && length > 0) {
@@ -26,5 +26,5 @@ export default class GraphicalInterface {
26
26
  darkFrontendMode: boolean;
27
27
  swipeupPanelMode: SwipeupPanelMode;
28
28
  contactPanelVisible: boolean;
29
- swipeupPanelContent: 'selector' | 'features' | 'menu' | 'drawing' | 'offline' | null;
29
+ swipeupPanelContent: 'selector' | 'features' | 'menu' | 'drawing' | 'offline' | 'share' | null;
30
30
  }