@geogirafe/lib-geoportal 1.2.0-dev.2750042516 → 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.
package/LICENSE CHANGED
@@ -246,6 +246,7 @@ From:
246
246
  - https://www.svgrepo.com/svg/310166/thumb-dislike
247
247
  - https://www.svgrepo.com/svg/309871/photo-filter
248
248
  - https://www.svgrepo.com/svg/309651/full-screen-zoom
249
+ - https://www.svgrepo.com/svg/310134/text-font
249
250
 
250
251
 
251
252
  The MIT License (MIT)
@@ -524,6 +524,8 @@
524
524
  "system": "System",
525
525
  "Take measure": "Messen",
526
526
  "Test for Translation": "Test für Übersetzung",
527
+ "Text": "Text",
528
+ "Text to draw": "Zu zeichnender Text",
527
529
  "Thanks for your message. If necessary, somebody will enter in contact with you as soon as possible.": "Vielen Dank für Ihre Nachricht. Falls erforderlich, wird sich so schnell wie möglich jemand mit Ihnen in Verbindung setzen.",
528
530
  "The CSV export has been truncated to the maximum number of features allowed for download": "Der CSV-Export wurde auf die maximal zulässige Anzahl herunterladbarer Objekte begrenzt.",
529
531
  "The following link will be sent with your message. It contains your current configuration and will help us to understand your problem.": "Der folgende Link wird mit Ihrer Nachricht gesendet. Er enthält Ihre aktuelle Konfiguration und hilft uns, Ihr Problem zu verstehen.",
@@ -527,6 +527,8 @@
527
527
  "system": "System",
528
528
  "Take measure": "Take measure",
529
529
  "Test for Translation": "Test for Translation",
530
+ "Text": "Text",
531
+ "Text to draw": "Text to draw",
530
532
  "Thanks for your message. If necessary, somebody will enter in contact with you as soon as possible.": "Thanks for your message. If necessary, someone will get in touch with you as soon as possible.",
531
533
  "The CSV export has been truncated to the maximum number of features allowed for download": "The CSV export has been truncated to the maximum number of features allowed for download.",
532
534
  "The following link will be sent with your message. It contains your current configuration and will help us to understand your problem.": "The following link will be sent with your message. It contains your current configuration and will help us to understand your problem.",
@@ -524,6 +524,8 @@
524
524
  "system": "Système",
525
525
  "Take measure": "Prendre une mesure",
526
526
  "Test for Translation": "Test de traduction",
527
+ "Text": "Texte",
528
+ "Text to draw": "Texte à dessiner",
527
529
  "Thanks for your message. If necessary, somebody will enter in contact with you as soon as possible.": "Merci pour votre message. Si nécessaire, quelqu'un prendra contact avec vous dès que possible.",
528
530
  "The CSV export has been truncated to the maximum number of features allowed for download": "L’export CSV a été limité au nombre maximal d’entités autorisées au téléchargement.",
529
531
  "The following link will be sent with your message. It contains your current configuration and will help us to understand your problem.": "Le lien ci-dessous sera envoyé avec votre message. Il contient votre configuration courante et nous aidera à analyser votre demande.",
@@ -524,6 +524,8 @@
524
524
  "system": "Sistema",
525
525
  "Take measure": "Prendi misura",
526
526
  "Test for Translation": "Test di traduzione",
527
+ "Text": "Testo",
528
+ "Text to draw": "Testo da disegnare",
527
529
  "Thanks for your message. If necessary, somebody will enter in contact with you as soon as possible.": "Grazie per il tuo messaggio. Se necessario, qualcuno ti contatterà il prima possibile.",
528
530
  "The CSV export has been truncated to the maximum number of features allowed for download": "L'esportazione CSV è stata limitata al numero massimo di elementi consentiti per il download.",
529
531
  "The following link will be sent with your message. It contains your current configuration and will help us to understand your problem.": "Il seguente link verrà inviato con il tuo messaggio. Contiene la tua configurazione attuale e ci aiuterà a capire il problema.",
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="800" height="800" viewBox="0 0 24 24"><title>ic_fluent_text_font_24_regular</title><path fill="#212121" fill-rule="nonzero" d="M14.503 6a.75.75 0 0 1 .697.474l5.555 14.03h.494a.75.75 0 0 1 0 1.5H18.75a.75.75 0 0 1 0-1.5h.392l-1.19-3.004h-6.91L9.85 20.504h.4a.75.75 0 0 1 0 1.5h-2.5a.75.75 0 1 1 0-1.5h.486l5.57-14.03A.75.75 0 0 1 14.503 6m0 2.785L11.637 16h5.72zM7 2a.75.75 0 0 1 .703.49l3.187 8.619-.828 2.086-.627-1.694H4.564L3.45 14.51a.75.75 0 0 1-1.407-.521l4.253-11.5A.75.75 0 0 1 7 2m0 2.913L5.118 10h3.763z"/></svg>
@@ -14,7 +14,7 @@ input{border:1px solid var(--bkg-color-grad1);width:inherit;background-color:var
14
14
  button.selected{background-color:#daebff!important;border:1px solid #54a4ff!important}#toolbar{box-sizing:border-box;grid-template-columns:repeat(5,1fr);gap:20px;width:100%;display:grid}#toolbar>button{box-sizing:border-box;filter:drop-shadow(0 4px 7px #0004382c);background-color:#fff;border-radius:8px;padding:10px}#toolbar>button>img{opacity:.7;width:100%}.icon{width:1.5rem;height:1.5rem}.gg-icon-button:hover{background-color:#0000}#panel{font-size:1.5em}#drawingList{gap:10px}#drawingList .girafe{filter:drop-shadow(0 4px 7px #0004381c);background:#fff;border:none;border-radius:8px;padding:10px}#optionsTitle{text-align:center;filter:drop-shadow(0 4px 7px #0004381c);color:#4553ff;border-radius:8px;width:100%;margin-bottom:20px;padding:10px;font-size:1.6rem}.gg-input:hover{filter:drop-shadow(0 4px 7px #4c58ff70);background-color:#fff}#optionsColorLine{flex-direction:column;display:flex}#optionsColorLine>div{text-align:end;width:100%}.selectable-label{font-size:1.3em}#optionsNameFontSize,#optionsMeasuresFontSize,#optionsStrokeWidth,#line-style,#arrow-style,#arrow-position{text-align:right;border:1px solid #e3e3e3;padding:1px;font-size:1em}.popup.popup_top{translate:15px -15px}#optionsExportLine{display:none}
15
15
  </style>
16
16
  <style>${this.customStyle}</style>
17
- <link rel="stylesheet" href="lib/vanilla-picker/vanilla-picker.csp.css"><link rel="stylesheet" href="styles/girafecolorpicker.css"><div id="panel"><div><div id="toolbar"><button id="disable" tip="Disable drawing" class="gg-icon-button gg-medium"><img alt="select icon" src="icons/arrow.svg"></button> <button id="point" tip="Point" class="gg-icon-button gg-medium"><img alt="point icon" src="icons/point.svg"></button> <button id="line" tip="Line" class="gg-icon-button gg-medium"><img alt="polyline icon" src="icons/polyline.svg"></button> <button id="polygon" tip="Polygon" class="gg-icon-button gg-medium"><img alt="polygon icon" src="icons/polygon.svg"></button> <button id="square" tip="Square" class="gg-icon-button gg-medium"><img alt="square icon" src="icons/square.svg"></button> <button id="rectangle" tip="Rectangle" class="gg-icon-button gg-medium"><img alt="rectangle icon" src="icons/rectangle.svg"></button> <button id="circle" tip="Circle" class="gg-icon-button gg-medium"><img alt="circle icon" src="icons/circle.svg"></button> <button id="freeline" tip="Freehand line" class="gg-icon-button gg-medium"><img alt="polyline-free icon" src="icons/polyline-free.svg"></button> <button id="freepolygon" tip="Freehand polygon" class="gg-icon-button gg-medium"><img alt="polygon-free icon" src="icons/polygon-free.svg"></button></div><div id="toolParametersContainer"><span i18n="Settings" class="title"></span><div class="toolParameters" data-tool="Polyline,Polygon"><girafe-fixed-dimension dimensionname="length" dimension="length" id="fixedLineLength" @fixed-dimension:value-changed="${(e) => this.fixedDimensionValueChangedHandler(e)}"></girafe-fixed-dimension></div><div class="toolParameters" data-tool="Square"><girafe-fixed-dimension dimensionname="side length" dimension="side" id="fixedSquareSide" @fixed-dimension:value-changed="${(e) => this.fixedDimensionValueChangedHandler(e)}"></girafe-fixed-dimension></div><div class="toolParameters" data-tool="Rectangle"><girafe-fixed-dimension dimensionname="width" dimension="width" id="fixedRectangleWidth" @fixed-dimension:value-changed="${(e) => this.fixedDimensionValueChangedHandler(e)}"></girafe-fixed-dimension><girafe-fixed-dimension dimensionname="height" dimension="height" id="fixedRectangleHeight" @fixed-dimension:value-changed="${(e) => this.fixedDimensionValueChangedHandler(e)}"></girafe-fixed-dimension></div></div></div><div id="drawingListContainer"><div class="titleContainer"><span i18n="Drawing List" class="title"></span> <button class="gg-icon-button icon" tip="Delete all shapes" onclick="${() => this.deleteAllFeatures()}" ?disabled="${this.drawingState.features.length === 0}">${this.htmlUnsafe(this.trashIcon)}</button></div><div id="drawingList">${this.drawingState.features.map(feature => uHtmlFor(feature, feature.id) `<div id="${'f-' + feature.id}" class="girafe"><button id="${'f-select-' + feature.id}" class="gg-icon-button gg-small" tip="Select to change appearance" onclick="${() => this.onToggleFeatureSelection(feature)}"><div class="icon">${this.htmlUnsafe(feature.selected ? this.checkedIcon : this.noCheckedIcon)}</div></button><div class="label-container"><button class="${feature.selected ? 'selectable-label gg-icon-button gg-small gg-selected' : 'selectable-label gg-icon-button gg-small gg-opacity'}" onclick="${() => this.onToggleFeatureSelection(feature)}"><span id="${'f-name-' + feature.id}">${feature.name}</span></button></div><button class="gg-icon-button icon" tip="Center on shape" onclick="${() => this.olDrawing.centerViewOnFeature(feature)}">${this.htmlUnsafe(this.locateIcon)}</button> <button class="gg-icon-button icon" tip="Delete shape" onclick="${() => this.deleteFeature(feature)}">${this.htmlUnsafe(this.trashIcon)}</button></div>`)}</div></div><div id="options" class="${this.selectedFeatures.length > 0 ? '' : 'disabled'}"><fieldset class="field-wrapper" disabled="${this.selectedFeatures.length <= 0}"><input id="optionsTitle" value="${this.getOptionsTitle()}" class="gg-input" disabled="${this.selectedFeatures.length > 1}"><div id="optionsText"><div id="optionsTextLines"><span class="label first" i18n="Name">Name</span> <button class="gg-icon-button icon" id="visibleIconName" tip="Show or hide shape name" onclick="${() => this.toggleNameVisibility()}">${this.htmlUnsafe(this.isDisplayNameEnabled() ? this.visibleIcon : this.notVisibleIcon)}</button> <span class="label" i18n="Font">Font</span> <input id="optionsNameFontSize" class="gg-input" type="number" min="1" value="${this.selectedFeatures[0]?.nameFontSize}" oninput="${() => this.onOptionsChange()}"> <span class="unit">pt</span> <button class="icon colorPicker" id="nameColorPicker"></button> <span class="label first" i18n="Measures">Measures</span> <select id="measure-information" class="gg-select" tip="${this.measureInformationTooltip}" onchange="${() => this.onOptionsChange()}">${Object.entries(this.measureInformations).map((entry) => uHtml `<option selected="${this.selectedFeatureMeasureInformation === entry[0]}" value="${entry[0]}"><span i18n="${entry[1]}">${entry[0]}</span></option>`)}</select> <span class="label" i18n="Font">Font</span> <input id="optionsMeasuresFontSize" class="gg-input" type="number" min="1" value="${this.selectedFeatures[0]?.measureFontSize}" oninput="${() => this.onOptionsChange()}"> <span class="unit">pt</span> <button class="icon colorPicker" id="measureColorPicker"></button></div></div><hr><div id="optionsColor"><div id="optionsColorLine"><div><span class="text-end" i18n="Stroke color">Stroke color</span> <button class="icon colorPicker" id="strokePicker"></button></div><div><span class="${this.isFillColorEnabled() ? 'text-end' : 'text-end disabled'}" id="fillPickerSpan" i18n="Fill color">Fill color</span> <button class="icon colorPicker" disabled="${!this.isFillColorEnabled()}" id="fillPicker"></button></div></div></div><hr><div id="optionsLine"><div id="optionsLineLines"><span i18n="Line width">Line width</span> <input id="optionsStrokeWidth" type="number" class="gg-input" min="1" value="${this.selectedFeatures[0]?.strokeWidth}" oninput="${() => this.onOptionsChange()}"> <span class="unit">px</span> <select id="line-style" class="gg-select" onchange="${() => this.onOptionsChange()}">${Object.entries(this.lineStyles).map((entry) => uHtml `<option selected="${this.selectedFeatures[0]?.lineStroke === entry[0]}" value="${entry[0]}"><span>${entry[1]}</span></option>`)}</select></div></div><hr><div id="optionsArrow"><div class="${this.isLineStyleEnabled() ? '' : 'disabled'}" id="optionsArrowStyles"><span i18n="Arrows style">Arrows style</span> <select id="arrow-style" class="gg-select" onchange="${() => this.onArrowsChange()}">${Object.entries(this.arrowStyles).map((entry) => uHtml `<option selected="${this.selectedFeatures[0]?.arrowStyle === entry[0]}" value="${entry[0]}"><span>${entry[1]}</span></option>`)}</select> <select id="arrow-position" class="gg-select" onchange="${() => this.onArrowsChange()}">${Object.entries(this.arrowPositions).map((entry) => uHtml `<option selected="${this.selectedFeatures[0]?.arrowPosition === entry[0]}" value="${entry[0]}"><span>${entry[1]}</span></option>`)}</select></div></div><hr><div id="optionsExport"><div id="optionsExportLine"><label for="export" class="gg-label" i18n="Export">Export</label> <button class="gg-button" onclick="${() => this.exportSelectedFeatures('geojson')}">GeoJson</button> <button class="gg-button" onclick="${() => this.exportSelectedFeatures('kml')}">KML</button> <button class="gg-button" onclick="${() => this.exportSelectedFeatures('gpx')}">GPX</button></div></div></fieldset></div></div>
17
+ <link rel="stylesheet" href="lib/vanilla-picker/vanilla-picker.csp.css"><link rel="stylesheet" href="styles/girafecolorpicker.css"><div id="panel"><div><div id="toolbar"><button id="disable" tip="Disable drawing" class="gg-icon-button gg-medium"><img alt="select icon" src="icons/arrow.svg"></button> <button id="point" tip="Point" class="gg-icon-button gg-medium"><img alt="point icon" src="icons/point.svg"></button> <button id="line" tip="Line" class="gg-icon-button gg-medium"><img alt="polyline icon" src="icons/polyline.svg"></button> <button id="polygon" tip="Polygon" class="gg-icon-button gg-medium"><img alt="polygon icon" src="icons/polygon.svg"></button> <button id="square" tip="Square" class="gg-icon-button gg-medium"><img alt="square icon" src="icons/square.svg"></button> <button id="rectangle" tip="Rectangle" class="gg-icon-button gg-medium"><img alt="rectangle icon" src="icons/rectangle.svg"></button> <button id="circle" tip="Circle" class="gg-icon-button gg-medium"><img alt="circle icon" src="icons/circle.svg"></button> <button id="freeline" tip="Freehand line" class="gg-icon-button gg-medium"><img alt="polyline-free icon" src="icons/polyline-free.svg"></button> <button id="freepolygon" tip="Freehand polygon" class="gg-icon-button gg-medium"><img alt="polygon-free icon" src="icons/polygon-free.svg"></button> <button id="text" tip="Text" class="gg-icon-button gg-medium"><img alt="text icon" src="icons/text-font.svg"></button></div><div id="toolParametersContainer"><span i18n="Settings" class="title"></span><div class="toolParameters" data-tool="Polyline,Polygon"><girafe-fixed-dimension dimensionname="length" dimension="length" id="fixedLineLength" @fixed-dimension:value-changed="${(e) => this.fixedDimensionValueChangedHandler(e)}"></girafe-fixed-dimension></div><div class="toolParameters" data-tool="Square"><girafe-fixed-dimension dimensionname="side length" dimension="side" id="fixedSquareSide" @fixed-dimension:value-changed="${(e) => this.fixedDimensionValueChangedHandler(e)}"></girafe-fixed-dimension></div><div class="toolParameters" data-tool="Rectangle"><girafe-fixed-dimension dimensionname="width" dimension="width" id="fixedRectangleWidth" @fixed-dimension:value-changed="${(e) => this.fixedDimensionValueChangedHandler(e)}"></girafe-fixed-dimension><girafe-fixed-dimension dimensionname="height" dimension="height" id="fixedRectangleHeight" @fixed-dimension:value-changed="${(e) => this.fixedDimensionValueChangedHandler(e)}"></girafe-fixed-dimension></div><div class="toolParameters" data-tool="Text"><label class="gg-label" for="drawingText" i18n="Text to draw">Text to draw</label> <input id="drawingText" class="gg-input" value="${this.drawingText}" oninput="${(e) => this.drawingTextChangedHandler(e)}"></div></div></div><div id="drawingListContainer"><div class="titleContainer"><span i18n="Drawing List" class="title"></span> <button class="gg-icon-button icon" tip="Delete all shapes" onclick="${() => this.deleteAllFeatures()}" ?disabled="${this.drawingState.features.length === 0}">${this.htmlUnsafe(this.trashIcon)}</button></div><div id="drawingList">${this.drawingState.features.map(feature => uHtmlFor(feature, feature.id) `<div id="${'f-' + feature.id}" class="girafe"><button id="${'f-select-' + feature.id}" class="gg-icon-button gg-small" tip="Select to change appearance" onclick="${() => this.onToggleFeatureSelection(feature)}"><div class="icon">${this.htmlUnsafe(feature.selected ? this.checkedIcon : this.noCheckedIcon)}</div></button><div class="label-container"><button class="${feature.selected ? 'selectable-label gg-icon-button gg-small gg-selected' : 'selectable-label gg-icon-button gg-small gg-opacity'}" onclick="${() => this.onToggleFeatureSelection(feature)}"><span id="${'f-name-' + feature.id}">${feature.name}</span></button></div><button class="gg-icon-button icon" tip="Center on shape" onclick="${() => this.olDrawing.centerViewOnFeature(feature)}">${this.htmlUnsafe(this.locateIcon)}</button> <button class="gg-icon-button icon" tip="Delete shape" onclick="${() => this.deleteFeature(feature)}">${this.htmlUnsafe(this.trashIcon)}</button></div>`)}</div></div><div id="options" class="${this.selectedFeatures.length > 0 ? '' : 'disabled'}"><fieldset class="field-wrapper" disabled="${this.selectedFeatures.length <= 0}"><input id="optionsTitle" value="${this.getOptionsTitle()}" class="gg-input" disabled="${this.selectedFeatures.length > 1}"><div id="optionsText"><div id="optionsTextLines"><span class="label first" i18n="Name">Name</span> <button class="gg-icon-button icon" id="visibleIconName" tip="Show or hide shape name" onclick="${() => this.toggleNameVisibility()}">${this.htmlUnsafe(this.isDisplayNameEnabled() ? this.visibleIcon : this.notVisibleIcon)}</button> <span class="label" i18n="Font">Font</span> <input id="optionsNameFontSize" class="gg-input" type="number" min="1" value="${this.selectedFeatures[0]?.nameFontSize}" oninput="${() => this.onOptionsChange()}"> <span class="unit">pt</span> <button class="icon colorPicker" id="nameColorPicker"></button> <span class="${this.isMeasureInformationEnabled() ? 'label first' : 'label first disabled'}" i18n="Measures">Measures</span> <select id="measure-information" class="gg-select" tip="${this.measureInformationTooltip}" disabled="${!this.isMeasureInformationEnabled()}" onchange="${() => this.onOptionsChange()}">${Object.entries(this.measureInformations).map((entry) => uHtml `<option selected="${this.selectedFeatureMeasureInformation === entry[0]}" value="${entry[0]}"><span i18n="${entry[1]}">${entry[0]}</span></option>`)}</select> <span class="${this.isMeasureInformationEnabled() ? 'label' : 'label disabled'}" i18n="Font">Font</span> <input id="optionsMeasuresFontSize" class="gg-input" type="number" min="1" value="${this.selectedFeatures[0]?.measureFontSize}" disabled="${!this.isMeasureInformationEnabled()}" oninput="${() => this.onOptionsChange()}"> <span class="${this.isMeasureInformationEnabled() ? 'unit' : 'unit disabled'}">pt</span> <button class="icon colorPicker" id="measureColorPicker" disabled="${!this.isMeasureInformationEnabled()}"></button></div></div><hr><div id="optionsColor"><div id="optionsColorLine"><div><span class="${this.isStrokeColorEnabled() ? 'text-end' : 'text-end disabled'}" i18n="Stroke color">Stroke color</span> <button class="icon colorPicker" disabled="${!this.isStrokeColorEnabled()}" id="strokePicker"></button></div><div><span class="${this.isFillColorEnabled() ? 'text-end' : 'text-end disabled'}" id="fillPickerSpan" i18n="Fill color">Fill color</span> <button class="icon colorPicker" disabled="${!this.isFillColorEnabled()}" id="fillPicker"></button></div></div></div><hr><div id="optionsLine"><div class="${this.isLineStyleEnabled() ? '' : 'disabled'}" id="optionsLineLines"><span i18n="Line width">Line width</span> <input id="optionsStrokeWidth" type="number" class="gg-input" min="1" value="${this.selectedFeatures[0]?.strokeWidth}" oninput="${() => this.onOptionsChange()}"> <span class="unit">px</span> <select id="line-style" class="gg-select" onchange="${() => this.onOptionsChange()}">${Object.entries(this.lineStyles).map((entry) => uHtml `<option selected="${this.selectedFeatures[0]?.lineStroke === entry[0]}" value="${entry[0]}"><span>${entry[1]}</span></option>`)}</select></div></div><hr><div id="optionsArrow"><div class="${this.isLineArrowEnabled() ? '' : 'disabled'}" id="optionsArrowStyles"><span i18n="Arrows style">Arrows style</span> <select id="arrow-style" class="gg-select" onchange="${() => this.onArrowsChange()}">${Object.entries(this.arrowStyles).map((entry) => uHtml `<option selected="${this.selectedFeatures[0]?.arrowStyle === entry[0]}" value="${entry[0]}"><span>${entry[1]}</span></option>`)}</select> <select id="arrow-position" class="gg-select" onchange="${() => this.onArrowsChange()}">${Object.entries(this.arrowPositions).map((entry) => uHtml `<option selected="${this.selectedFeatures[0]?.arrowPosition === entry[0]}" value="${entry[0]}"><span>${entry[1]}</span></option>`)}</select></div></div><hr><div id="optionsExport"><div id="optionsExportLine"><label for="export" class="gg-label" i18n="Export">Export</label> <button class="gg-button" onclick="${() => this.exportSelectedFeatures('geojson')}">GeoJson</button> <button class="gg-button" onclick="${() => this.exportSelectedFeatures('kml')}">KML</button> <button class="gg-button" onclick="${() => this.exportSelectedFeatures('gpx')}">GPX</button></div></div></fieldset></div></div>
18
18
  ${this.htmlUnsafe(this.feedbackTemplateHtml ?? '')}`;
19
19
  };
20
20
  constructor() {
@@ -44,6 +44,7 @@ export default class DrawingComponent extends GirafeHTMLElement implements IGira
44
44
  olDrawing: OlDrawing;
45
45
  cesiumDrawing: CesiumDrawing;
46
46
  fixedLengthEnabled: boolean;
47
+ drawingText: string;
47
48
  batchCreateMode: boolean;
48
49
  constructor(name?: string);
49
50
  render(): void;
@@ -55,6 +56,7 @@ export default class DrawingComponent extends GirafeHTMLElement implements IGira
55
56
  setTool(tool?: DrawingShape | null): void;
56
57
  showToolParameters(tool?: DrawingShape | null): void;
57
58
  protected fixedDimensionValueChangedHandler(e: CustomEvent): void;
59
+ protected drawingTextChangedHandler(e: Event): void;
58
60
  protected connectedCallback(): void;
59
61
  togglePanel(visible: boolean): void;
60
62
  get selectedFeatures(): DrawingFeature[];
@@ -67,8 +69,12 @@ export default class DrawingComponent extends GirafeHTMLElement implements IGira
67
69
  deactivateLayerInTreeAnMap(): void;
68
70
  protected getOptionsTitle(): string;
69
71
  protected isDisplayNameEnabled(): boolean;
70
- protected isLineStyleEnabled(): boolean;
72
+ protected isLineArrowEnabled(): boolean;
71
73
  protected isFillColorEnabled(): boolean;
74
+ protected isStrokeColorEnabled(): boolean;
75
+ protected isLineStyleEnabled(): boolean;
76
+ protected isMeasureInformationEnabled(): boolean;
77
+ private selectedFeaturesHasTextFeature;
72
78
  deleteFeature(feature: DrawingFeature): Promise<void>;
73
79
  deleteAllFeatures(): Promise<void>;
74
80
  onOptionsChange(): void;
@@ -76,5 +82,6 @@ export default class DrawingComponent extends GirafeHTMLElement implements IGira
76
82
  toggleNameVisibility(): void;
77
83
  private warnWhenInWebMercator;
78
84
  exportSelectedFeatures(format: 'geojson' | 'kml' | 'gpx'): void;
85
+ private onRemovalRequested;
79
86
  }
80
87
  export {};
@@ -25,6 +25,7 @@ import visibleIcon from './assets/visible.svg?raw';
25
25
  import notVisibleIcon from './assets/notVisible.svg?raw';
26
26
  import LayerDrawing from '../../models/layers/layerdrawing.js';
27
27
  import { UsedInTemplateOnly } from '../../decorators.js';
28
+ import { noop } from '../../tools/utils/async.js';
28
29
  export const DrawingStateLocation = 'drawing';
29
30
  export default class DrawingComponent extends GirafeHTMLElement {
30
31
  templateUrl = null;
@@ -36,7 +37,7 @@ export default class DrawingComponent extends GirafeHTMLElement {
36
37
  input{border:1px solid var(--bkg-color-grad1);width:inherit;background-color:var(--bkg-color-input);padding-right:0}input:focus{border:1px solid var(--lt-color-gray-900)}button{border:solid 1px var(--bkg-color);color:var(--text-color);background-color:var(--bkg-color);cursor:pointer;padding:0}button:hover{border:solid 1px var(--text-color-grad2)}button.selected{border:solid 1px var(--text-color-grad2);background-color:var(--bkg-color-grad1);color:var(--bkg-color)}#toolbar{flex-wrap:wrap;display:flex}.label-container{flex-grow:1}.text-end{text-align:end}.icon{width:1.2rem;height:1.2rem;fill:var(--text-color);flex-shrink:0;margin:3px}.icon:hover{fill:var(--text-color-grad2)}.gg-icon-button:hover{border:none}.colorPicker{border:1px solid var(--text-color);border-radius:5px}.picker_wrapper{transform:translate(-250px)}.picker_arrow{transform:translate(200px)rotate(90deg)scaleX(-1)!important}.picker_wrapper,.picker_arrow:after,.picker_arrow:before{background-color:var(--bkg-color)!important}.picker_wrapper,.picker_arrow:before{box-shadow:0 0 10px 1px var(--text-color-grad1)!important}button:disabled,input:disabled,.disabled{pointer-events:none;opacity:.5}fieldset.field-wrapper{border:0;margin:0;padding:0}#panel{height:100%;color:var(--text-color);flex-direction:column;display:flex}#panel>div{padding:1rem}#toolParametersContainer{& .title{margin-top:.5rem;margin-bottom:.75rem;display:inline-block}}.toolParameters{flex-direction:column;justify-content:left;row-gap:.25rem;display:flex}.toolParameters>label{padding:0 5px}#drawingListContainer{flex-grow:1;overflow:hidden auto;padding-top:0!important;& .titleContainer{margin-bottom:1rem;display:flex;& .title{flex-grow:1;align-items:center;display:flex}}}#drawingList{flex-flow:column;gap:.15rem;display:flex}#drawingList .girafe{border:solid 1px var(--text-color);flex-wrap:wrap;align-items:center;padding:.3rem;display:flex;position:relative}#drawingList .girafe span{flex-grow:1;padding-left:5px}#options{box-shadow:0px -2px 3px -1px var(--text-color-grad1);z-index:100;flex-direction:column;display:flex;position:relative}#optionsTitle{text-align:center;margin-bottom:20px;font-size:1.2rem}#options hr{width:100%;margin:8px 0}#optionsTextLines,#optionsColorLine,#optionsLineLines{align-items:center;display:grid}#optionsText{margin:auto}#optionsTextLines{grid-template-columns:.825fr 2.5fr 1fr 4em .5fr .5fr}#optionsTextLines button:hover{border:none}#optionsColor,#optionsLine,#optionsExport,#optionsArrow{margin:auto}#optionsColorLine{display:flex}#optionsColorLine>div{align-items:center;width:100%;display:flex}#optionsLineLines,#optionsArrowStyle{grid-template-columns:3fr 4em 1fr auto}#optionsExportLine{grid-template-columns:60px 60px 45px 45px;gap:.2rem;display:flex}span.label{padding-left:.5rem;padding-right:.5rem;&.first{padding-left:0}}span.unit{padding-left:.5rem}
37
38
  </style>
38
39
  <style>${this.customStyle}</style>
39
- <link rel="stylesheet" href="lib/vanilla-picker/vanilla-picker.csp.css"><link rel="stylesheet" href="styles/girafecolorpicker.css"><div id="panel"><div><div id="toolbar"><button id="disable" tip="Disable drawing" class="gg-icon-button gg-medium"><img alt="select icon" src="icons/arrow.svg"></button> <button id="point" tip="Point" class="gg-icon-button gg-medium"><img alt="point icon" src="icons/point.svg"></button> <button id="line" tip="Line" class="gg-icon-button gg-medium"><img alt="polyline icon" src="icons/polyline.svg"></button> <button id="polygon" tip="Polygon" class="gg-icon-button gg-medium"><img alt="polygon icon" src="icons/polygon.svg"></button> <button id="square" tip="Square" class="gg-icon-button gg-medium"><img alt="square icon" src="icons/square.svg"></button> <button id="rectangle" tip="Rectangle" class="gg-icon-button gg-medium"><img alt="rectangle icon" src="icons/rectangle.svg"></button> <button id="circle" tip="Circle" class="gg-icon-button gg-medium"><img alt="circle icon" src="icons/circle.svg"></button> <button id="freeline" tip="Freehand line" class="gg-icon-button gg-medium"><img alt="polyline-free icon" src="icons/polyline-free.svg"></button> <button id="freepolygon" tip="Freehand polygon" class="gg-icon-button gg-medium"><img alt="polygon-free icon" src="icons/polygon-free.svg"></button></div><div id="toolParametersContainer"><span i18n="Settings" class="title"></span><div class="toolParameters" data-tool="Polyline,Polygon"><girafe-fixed-dimension dimensionname="length" dimension="length" id="fixedLineLength" @fixed-dimension:value-changed="${(e) => this.fixedDimensionValueChangedHandler(e)}"></girafe-fixed-dimension></div><div class="toolParameters" data-tool="Square"><girafe-fixed-dimension dimensionname="side length" dimension="side" id="fixedSquareSide" @fixed-dimension:value-changed="${(e) => this.fixedDimensionValueChangedHandler(e)}"></girafe-fixed-dimension></div><div class="toolParameters" data-tool="Rectangle"><girafe-fixed-dimension dimensionname="width" dimension="width" id="fixedRectangleWidth" @fixed-dimension:value-changed="${(e) => this.fixedDimensionValueChangedHandler(e)}"></girafe-fixed-dimension><girafe-fixed-dimension dimensionname="height" dimension="height" id="fixedRectangleHeight" @fixed-dimension:value-changed="${(e) => this.fixedDimensionValueChangedHandler(e)}"></girafe-fixed-dimension></div></div></div><div id="drawingListContainer"><div class="titleContainer"><span i18n="Drawing List" class="title"></span> <button class="gg-icon-button icon" tip="Delete all shapes" onclick="${() => this.deleteAllFeatures()}" ?disabled="${this.drawingState.features.length === 0}">${this.htmlUnsafe(this.trashIcon)}</button></div><div id="drawingList">${this.drawingState.features.map(feature => uHtmlFor(feature, feature.id) `<div id="${'f-' + feature.id}" class="girafe"><button id="${'f-select-' + feature.id}" class="gg-icon-button gg-small" tip="Select to change appearance" onclick="${() => this.onToggleFeatureSelection(feature)}"><div class="icon">${this.htmlUnsafe(feature.selected ? this.checkedIcon : this.noCheckedIcon)}</div></button><div class="label-container"><button class="${feature.selected ? 'selectable-label gg-icon-button gg-small gg-selected' : 'selectable-label gg-icon-button gg-small gg-opacity'}" onclick="${() => this.onToggleFeatureSelection(feature)}"><span id="${'f-name-' + feature.id}">${feature.name}</span></button></div><button class="gg-icon-button icon" tip="Center on shape" onclick="${() => this.olDrawing.centerViewOnFeature(feature)}">${this.htmlUnsafe(this.locateIcon)}</button> <button class="gg-icon-button icon" tip="Delete shape" onclick="${() => this.deleteFeature(feature)}">${this.htmlUnsafe(this.trashIcon)}</button></div>`)}</div></div><div id="options" class="${this.selectedFeatures.length > 0 ? '' : 'disabled'}"><fieldset class="field-wrapper" disabled="${this.selectedFeatures.length <= 0}"><input id="optionsTitle" value="${this.getOptionsTitle()}" class="gg-input" disabled="${this.selectedFeatures.length > 1}"><div id="optionsText"><div id="optionsTextLines"><span class="label first" i18n="Name">Name</span> <button class="gg-icon-button icon" id="visibleIconName" tip="Show or hide shape name" onclick="${() => this.toggleNameVisibility()}">${this.htmlUnsafe(this.isDisplayNameEnabled() ? this.visibleIcon : this.notVisibleIcon)}</button> <span class="label" i18n="Font">Font</span> <input id="optionsNameFontSize" class="gg-input" type="number" min="1" value="${this.selectedFeatures[0]?.nameFontSize}" oninput="${() => this.onOptionsChange()}"> <span class="unit">pt</span> <button class="icon colorPicker" id="nameColorPicker"></button> <span class="label first" i18n="Measures">Measures</span> <select id="measure-information" class="gg-select" tip="${this.measureInformationTooltip}" onchange="${() => this.onOptionsChange()}">${Object.entries(this.measureInformations).map((entry) => uHtml `<option selected="${this.selectedFeatureMeasureInformation === entry[0]}" value="${entry[0]}"><span i18n="${entry[1]}">${entry[0]}</span></option>`)}</select> <span class="label" i18n="Font">Font</span> <input id="optionsMeasuresFontSize" class="gg-input" type="number" min="1" value="${this.selectedFeatures[0]?.measureFontSize}" oninput="${() => this.onOptionsChange()}"> <span class="unit">pt</span> <button class="icon colorPicker" id="measureColorPicker"></button></div></div><hr><div id="optionsColor"><div id="optionsColorLine"><div><span class="text-end" i18n="Stroke color">Stroke color</span> <button class="icon colorPicker" id="strokePicker"></button></div><div><span class="${this.isFillColorEnabled() ? 'text-end' : 'text-end disabled'}" id="fillPickerSpan" i18n="Fill color">Fill color</span> <button class="icon colorPicker" disabled="${!this.isFillColorEnabled()}" id="fillPicker"></button></div></div></div><hr><div id="optionsLine"><div id="optionsLineLines"><span i18n="Line width">Line width</span> <input id="optionsStrokeWidth" type="number" class="gg-input" min="1" value="${this.selectedFeatures[0]?.strokeWidth}" oninput="${() => this.onOptionsChange()}"> <span class="unit">px</span> <select id="line-style" class="gg-select" onchange="${() => this.onOptionsChange()}">${Object.entries(this.lineStyles).map((entry) => uHtml `<option selected="${this.selectedFeatures[0]?.lineStroke === entry[0]}" value="${entry[0]}"><span>${entry[1]}</span></option>`)}</select></div></div><hr><div id="optionsArrow"><div class="${this.isLineStyleEnabled() ? '' : 'disabled'}" id="optionsArrowStyles"><span i18n="Arrows style">Arrows style</span> <select id="arrow-style" class="gg-select" onchange="${() => this.onArrowsChange()}">${Object.entries(this.arrowStyles).map((entry) => uHtml `<option selected="${this.selectedFeatures[0]?.arrowStyle === entry[0]}" value="${entry[0]}"><span>${entry[1]}</span></option>`)}</select> <select id="arrow-position" class="gg-select" onchange="${() => this.onArrowsChange()}">${Object.entries(this.arrowPositions).map((entry) => uHtml `<option selected="${this.selectedFeatures[0]?.arrowPosition === entry[0]}" value="${entry[0]}"><span>${entry[1]}</span></option>`)}</select></div></div><hr><div id="optionsExport"><div id="optionsExportLine"><label for="export" class="gg-label" i18n="Export">Export</label> <button class="gg-button" onclick="${() => this.exportSelectedFeatures('geojson')}">GeoJson</button> <button class="gg-button" onclick="${() => this.exportSelectedFeatures('kml')}">KML</button> <button class="gg-button" onclick="${() => this.exportSelectedFeatures('gpx')}">GPX</button></div></div></fieldset></div></div>
40
+ <link rel="stylesheet" href="lib/vanilla-picker/vanilla-picker.csp.css"><link rel="stylesheet" href="styles/girafecolorpicker.css"><div id="panel"><div><div id="toolbar"><button id="disable" tip="Disable drawing" class="gg-icon-button gg-medium"><img alt="select icon" src="icons/arrow.svg"></button> <button id="point" tip="Point" class="gg-icon-button gg-medium"><img alt="point icon" src="icons/point.svg"></button> <button id="line" tip="Line" class="gg-icon-button gg-medium"><img alt="polyline icon" src="icons/polyline.svg"></button> <button id="polygon" tip="Polygon" class="gg-icon-button gg-medium"><img alt="polygon icon" src="icons/polygon.svg"></button> <button id="square" tip="Square" class="gg-icon-button gg-medium"><img alt="square icon" src="icons/square.svg"></button> <button id="rectangle" tip="Rectangle" class="gg-icon-button gg-medium"><img alt="rectangle icon" src="icons/rectangle.svg"></button> <button id="circle" tip="Circle" class="gg-icon-button gg-medium"><img alt="circle icon" src="icons/circle.svg"></button> <button id="freeline" tip="Freehand line" class="gg-icon-button gg-medium"><img alt="polyline-free icon" src="icons/polyline-free.svg"></button> <button id="freepolygon" tip="Freehand polygon" class="gg-icon-button gg-medium"><img alt="polygon-free icon" src="icons/polygon-free.svg"></button> <button id="text" tip="Text" class="gg-icon-button gg-medium"><img alt="text icon" src="icons/text-font.svg"></button></div><div id="toolParametersContainer"><span i18n="Settings" class="title"></span><div class="toolParameters" data-tool="Polyline,Polygon"><girafe-fixed-dimension dimensionname="length" dimension="length" id="fixedLineLength" @fixed-dimension:value-changed="${(e) => this.fixedDimensionValueChangedHandler(e)}"></girafe-fixed-dimension></div><div class="toolParameters" data-tool="Square"><girafe-fixed-dimension dimensionname="side length" dimension="side" id="fixedSquareSide" @fixed-dimension:value-changed="${(e) => this.fixedDimensionValueChangedHandler(e)}"></girafe-fixed-dimension></div><div class="toolParameters" data-tool="Rectangle"><girafe-fixed-dimension dimensionname="width" dimension="width" id="fixedRectangleWidth" @fixed-dimension:value-changed="${(e) => this.fixedDimensionValueChangedHandler(e)}"></girafe-fixed-dimension><girafe-fixed-dimension dimensionname="height" dimension="height" id="fixedRectangleHeight" @fixed-dimension:value-changed="${(e) => this.fixedDimensionValueChangedHandler(e)}"></girafe-fixed-dimension></div><div class="toolParameters" data-tool="Text"><label class="gg-label" for="drawingText" i18n="Text to draw">Text to draw</label> <input id="drawingText" class="gg-input" value="${this.drawingText}" oninput="${(e) => this.drawingTextChangedHandler(e)}"></div></div></div><div id="drawingListContainer"><div class="titleContainer"><span i18n="Drawing List" class="title"></span> <button class="gg-icon-button icon" tip="Delete all shapes" onclick="${() => this.deleteAllFeatures()}" ?disabled="${this.drawingState.features.length === 0}">${this.htmlUnsafe(this.trashIcon)}</button></div><div id="drawingList">${this.drawingState.features.map(feature => uHtmlFor(feature, feature.id) `<div id="${'f-' + feature.id}" class="girafe"><button id="${'f-select-' + feature.id}" class="gg-icon-button gg-small" tip="Select to change appearance" onclick="${() => this.onToggleFeatureSelection(feature)}"><div class="icon">${this.htmlUnsafe(feature.selected ? this.checkedIcon : this.noCheckedIcon)}</div></button><div class="label-container"><button class="${feature.selected ? 'selectable-label gg-icon-button gg-small gg-selected' : 'selectable-label gg-icon-button gg-small gg-opacity'}" onclick="${() => this.onToggleFeatureSelection(feature)}"><span id="${'f-name-' + feature.id}">${feature.name}</span></button></div><button class="gg-icon-button icon" tip="Center on shape" onclick="${() => this.olDrawing.centerViewOnFeature(feature)}">${this.htmlUnsafe(this.locateIcon)}</button> <button class="gg-icon-button icon" tip="Delete shape" onclick="${() => this.deleteFeature(feature)}">${this.htmlUnsafe(this.trashIcon)}</button></div>`)}</div></div><div id="options" class="${this.selectedFeatures.length > 0 ? '' : 'disabled'}"><fieldset class="field-wrapper" disabled="${this.selectedFeatures.length <= 0}"><input id="optionsTitle" value="${this.getOptionsTitle()}" class="gg-input" disabled="${this.selectedFeatures.length > 1}"><div id="optionsText"><div id="optionsTextLines"><span class="label first" i18n="Name">Name</span> <button class="gg-icon-button icon" id="visibleIconName" tip="Show or hide shape name" onclick="${() => this.toggleNameVisibility()}">${this.htmlUnsafe(this.isDisplayNameEnabled() ? this.visibleIcon : this.notVisibleIcon)}</button> <span class="label" i18n="Font">Font</span> <input id="optionsNameFontSize" class="gg-input" type="number" min="1" value="${this.selectedFeatures[0]?.nameFontSize}" oninput="${() => this.onOptionsChange()}"> <span class="unit">pt</span> <button class="icon colorPicker" id="nameColorPicker"></button> <span class="${this.isMeasureInformationEnabled() ? 'label first' : 'label first disabled'}" i18n="Measures">Measures</span> <select id="measure-information" class="gg-select" tip="${this.measureInformationTooltip}" disabled="${!this.isMeasureInformationEnabled()}" onchange="${() => this.onOptionsChange()}">${Object.entries(this.measureInformations).map((entry) => uHtml `<option selected="${this.selectedFeatureMeasureInformation === entry[0]}" value="${entry[0]}"><span i18n="${entry[1]}">${entry[0]}</span></option>`)}</select> <span class="${this.isMeasureInformationEnabled() ? 'label' : 'label disabled'}" i18n="Font">Font</span> <input id="optionsMeasuresFontSize" class="gg-input" type="number" min="1" value="${this.selectedFeatures[0]?.measureFontSize}" disabled="${!this.isMeasureInformationEnabled()}" oninput="${() => this.onOptionsChange()}"> <span class="${this.isMeasureInformationEnabled() ? 'unit' : 'unit disabled'}">pt</span> <button class="icon colorPicker" id="measureColorPicker" disabled="${!this.isMeasureInformationEnabled()}"></button></div></div><hr><div id="optionsColor"><div id="optionsColorLine"><div><span class="${this.isStrokeColorEnabled() ? 'text-end' : 'text-end disabled'}" i18n="Stroke color">Stroke color</span> <button class="icon colorPicker" disabled="${!this.isStrokeColorEnabled()}" id="strokePicker"></button></div><div><span class="${this.isFillColorEnabled() ? 'text-end' : 'text-end disabled'}" id="fillPickerSpan" i18n="Fill color">Fill color</span> <button class="icon colorPicker" disabled="${!this.isFillColorEnabled()}" id="fillPicker"></button></div></div></div><hr><div id="optionsLine"><div class="${this.isLineStyleEnabled() ? '' : 'disabled'}" id="optionsLineLines"><span i18n="Line width">Line width</span> <input id="optionsStrokeWidth" type="number" class="gg-input" min="1" value="${this.selectedFeatures[0]?.strokeWidth}" oninput="${() => this.onOptionsChange()}"> <span class="unit">px</span> <select id="line-style" class="gg-select" onchange="${() => this.onOptionsChange()}">${Object.entries(this.lineStyles).map((entry) => uHtml `<option selected="${this.selectedFeatures[0]?.lineStroke === entry[0]}" value="${entry[0]}"><span>${entry[1]}</span></option>`)}</select></div></div><hr><div id="optionsArrow"><div class="${this.isLineArrowEnabled() ? '' : 'disabled'}" id="optionsArrowStyles"><span i18n="Arrows style">Arrows style</span> <select id="arrow-style" class="gg-select" onchange="${() => this.onArrowsChange()}">${Object.entries(this.arrowStyles).map((entry) => uHtml `<option selected="${this.selectedFeatures[0]?.arrowStyle === entry[0]}" value="${entry[0]}"><span>${entry[1]}</span></option>`)}</select> <select id="arrow-position" class="gg-select" onchange="${() => this.onArrowsChange()}">${Object.entries(this.arrowPositions).map((entry) => uHtml `<option selected="${this.selectedFeatures[0]?.arrowPosition === entry[0]}" value="${entry[0]}"><span>${entry[1]}</span></option>`)}</select></div></div><hr><div id="optionsExport"><div id="optionsExportLine"><label for="export" class="gg-label" i18n="Export">Export</label> <button class="gg-button" onclick="${() => this.exportSelectedFeatures('geojson')}">GeoJson</button> <button class="gg-button" onclick="${() => this.exportSelectedFeatures('kml')}">KML</button> <button class="gg-button" onclick="${() => this.exportSelectedFeatures('gpx')}">GPX</button></div></div></fieldset></div></div>
40
41
  ${this.htmlUnsafe(this.feedbackTemplateHtml ?? '')}`;
41
42
  };
42
43
  isPanelVisible = false;
@@ -60,7 +61,8 @@ ${this.htmlUnsafe(this.feedbackTemplateHtml ?? '')}`;
60
61
  { id: 'polygon', tool: DrawingShape.Polygon },
61
62
  { id: 'circle', tool: DrawingShape.Disk },
62
63
  { id: 'freeline', tool: DrawingShape.FreehandPolyline },
63
- { id: 'freepolygon', tool: DrawingShape.FreehandPolygon }
64
+ { id: 'freepolygon', tool: DrawingShape.FreehandPolygon },
65
+ { id: 'text', tool: DrawingShape.Text }
64
66
  ];
65
67
  toolSelected = null;
66
68
  arrowStyles = {
@@ -112,6 +114,8 @@ ${this.htmlUnsafe(this.feedbackTemplateHtml ?? '')}`;
112
114
  olDrawing;
113
115
  cesiumDrawing;
114
116
  fixedLengthEnabled = false;
117
+ // Text used by the "Text" drawing tool for the shapes it creates.
118
+ drawingText = '';
115
119
  // Batch Create mode is currently not used. Batch mode allows the user to create multiple shapes without re-selecting
116
120
  // the drawing tool. It possibly will be part of advanced drawing/editing tools.
117
121
  batchCreateMode = false;
@@ -168,6 +172,7 @@ ${this.htmlUnsafe(this.feedbackTemplateHtml ?? '')}`;
168
172
  this.olDrawing.registerInteractions();
169
173
  if (this.state.globe.loaded)
170
174
  this.cesiumDrawing.registerInteractions();
175
+ this.subscribe(/layers\.layersList\..*\.removalRequested/, (oldValue, value, parents) => this.onRemovalRequested(oldValue, value, parents));
171
176
  }
172
177
  unregisterEvents() {
173
178
  this.olDrawing.unregisterInteractions();
@@ -228,6 +233,11 @@ ${this.htmlUnsafe(this.feedbackTemplateHtml ?? '')}`;
228
233
  console.debug('Value changed for unknown Dimension with id', details.id);
229
234
  }
230
235
  }
236
+ drawingTextChangedHandler(e) {
237
+ this.drawingText = e.target.value;
238
+ this.olDrawing.setDrawingText(this.drawingText);
239
+ this.cesiumDrawing.setDrawingText(this.drawingText);
240
+ }
231
241
  connectedCallback() {
232
242
  super.connectedCallback();
233
243
  if (!this.state.extendedState[DrawingStateLocation]) {
@@ -244,6 +254,12 @@ ${this.htmlUnsafe(this.feedbackTemplateHtml ?? '')}`;
244
254
  this.cesiumDrawing = new CesiumDrawing(map, DrawingStateLocation, this.context);
245
255
  this.render();
246
256
  this.subscribe(`extendedState.${DrawingStateLocation}.features`, (olds, news) => this.onFeaturesChanged(olds, news));
257
+ // Keep the shape list and the options in sync when the selection is changed in the map
258
+ this.subscribe(new RegExp(String.raw `extendedState.${DrawingStateLocation}.features.*\.selected`, 'g'), () => {
259
+ if (this.isPanelVisible) {
260
+ this.refreshRender();
261
+ }
262
+ });
247
263
  this.subscribe('projection', (olds, news) => this.onProjectionChanged(olds, news));
248
264
  this.subscribe('globe.loaded', () => {
249
265
  if (this.state.globe.loaded && this.isPanelVisible) {
@@ -258,6 +274,9 @@ ${this.htmlUnsafe(this.feedbackTemplateHtml ?? '')}`;
258
274
  togglePanel(visible) {
259
275
  if (this.isPanelVisible == visible)
260
276
  return;
277
+ if (this.activeDrawingLayer?.removalRequested) {
278
+ this.deleteAllFeatures().then(noop);
279
+ }
261
280
  this.isPanelVisible = visible;
262
281
  if (this.isPanelVisible) {
263
282
  this.registerEvents();
@@ -369,11 +388,27 @@ ${this.htmlUnsafe(this.feedbackTemplateHtml ?? '')}`;
369
388
  isDisplayNameEnabled() {
370
389
  return this.selectedFeatures.some((f) => f.displayName);
371
390
  }
372
- isLineStyleEnabled() {
391
+ isLineArrowEnabled() {
373
392
  return this.selectedFeatures.some((f) => f.type == DrawingShape.Polyline || f.type == DrawingShape.FreehandPolyline);
374
393
  }
375
394
  isFillColorEnabled() {
376
- return !this.selectedFeatures.every((f) => f.isPointOrPolyline());
395
+ return this.selectedFeatures.every((f) => f.type == DrawingShape.Polygon ||
396
+ f.type == DrawingShape.FreehandPolygon ||
397
+ f.type == DrawingShape.Disk ||
398
+ f.type == DrawingShape.Rectangle ||
399
+ f.type == DrawingShape.Square);
400
+ }
401
+ isStrokeColorEnabled() {
402
+ return !this.selectedFeaturesHasTextFeature();
403
+ }
404
+ isLineStyleEnabled() {
405
+ return !this.selectedFeaturesHasTextFeature();
406
+ }
407
+ isMeasureInformationEnabled() {
408
+ return !this.selectedFeaturesHasTextFeature();
409
+ }
410
+ selectedFeaturesHasTextFeature() {
411
+ return this.selectedFeatures.some((f) => f.type == DrawingShape.Text);
377
412
  }
378
413
  async deleteFeature(feature) {
379
414
  const confirm = await window.gConfirm('Do you want to remove "${feature.name}" ?', 'Delete Feature', (translatedMessage) => {
@@ -393,6 +428,9 @@ ${this.htmlUnsafe(this.feedbackTemplateHtml ?? '')}`;
393
428
  this.drawingState.features = [];
394
429
  this.refreshRender();
395
430
  }
431
+ else {
432
+ this.activeDrawingLayer.removalRequested = false;
433
+ }
396
434
  }
397
435
  onOptionsChange() {
398
436
  const nameFontSize = parseInt(this.getById('optionsNameFontSize').value);
@@ -449,7 +487,7 @@ ${this.htmlUnsafe(this.feedbackTemplateHtml ?? '')}`;
449
487
  case 'kml':
450
488
  return download(new KML().writeFeatures(olFeatures, { featureProjection: this.state.projection }), fileName + '.kml', 'application/vnd.google-earth.kml+xml');
451
489
  case 'gpx':
452
- if (this.selectedFeatures.some((f) => !f.isPointOrPolyline())) {
490
+ if (this.selectedFeatures.some((f) => !f.isPointPolylineOrText())) {
453
491
  this.context.errorManager.pushMessage('export-gpx-error', 'The GPX format only supports points and polylines', 'warning');
454
492
  }
455
493
  else {
@@ -457,10 +495,18 @@ ${this.htmlUnsafe(this.feedbackTemplateHtml ?? '')}`;
457
495
  }
458
496
  }
459
497
  }
498
+ onRemovalRequested(_oldValue, value, parents) {
499
+ if (value === true && parents.removalRequested) {
500
+ this.deleteAllFeatures().then(noop);
501
+ }
502
+ }
460
503
  }
461
504
  __decorate([
462
505
  UsedInTemplateOnly()
463
506
  ], DrawingComponent.prototype, "fixedDimensionValueChangedHandler", null);
507
+ __decorate([
508
+ UsedInTemplateOnly()
509
+ ], DrawingComponent.prototype, "drawingTextChangedHandler", null);
464
510
  __decorate([
465
511
  UsedInTemplateOnly('Currently not used, will possibly be part of advanced editing/drawing tools')
466
512
  ], DrawingComponent.prototype, "onToggleBatchMode", null);
@@ -472,7 +518,16 @@ __decorate([
472
518
  ], DrawingComponent.prototype, "isDisplayNameEnabled", null);
473
519
  __decorate([
474
520
  UsedInTemplateOnly()
475
- ], DrawingComponent.prototype, "isLineStyleEnabled", null);
521
+ ], DrawingComponent.prototype, "isLineArrowEnabled", null);
476
522
  __decorate([
477
523
  UsedInTemplateOnly()
478
524
  ], DrawingComponent.prototype, "isFillColorEnabled", null);
525
+ __decorate([
526
+ UsedInTemplateOnly()
527
+ ], DrawingComponent.prototype, "isStrokeColorEnabled", null);
528
+ __decorate([
529
+ UsedInTemplateOnly()
530
+ ], DrawingComponent.prototype, "isLineStyleEnabled", null);
531
+ __decorate([
532
+ UsedInTemplateOnly()
533
+ ], DrawingComponent.prototype, "isMeasureInformationEnabled", null);
@@ -8,6 +8,7 @@ import LayerWmts from '../../../models/layers/layerwmts.js';
8
8
  import TreeViewElement from '../tools/treeviewelement.js';
9
9
  import { isSnappableLayer } from '../../../models/layers/snappablelayer.js';
10
10
  import WmsLegendHelper from '../../../tools/wms/wmslegendhelper.js';
11
+ import LayerDrawing from '../../../models/layers/layerdrawing.js';
11
12
  class TreeViewItemComponent extends TreeViewElement {
12
13
  templateUrl = null;
13
14
  styleUrls = null;
@@ -224,8 +225,14 @@ ${this.htmlUnsafe(this.feedbackTemplateHtml ?? '')}`;
224
225
  this.context.mapManager.zoomToExtent(this.layer.extent);
225
226
  }
226
227
  deleteLayer() {
227
- this.context.layerManager.toggleLayer(this.layer, 'off');
228
- this.removeFromParent();
228
+ if (this.layer instanceof LayerDrawing) {
229
+ this.layer.removalRequested = true;
230
+ this.context.stateManager.state.interface.drawingPanelVisible = true;
231
+ }
232
+ else {
233
+ this.context.layerManager.toggleLayer(this.layer, 'off');
234
+ this.removeFromParent();
235
+ }
229
236
  }
230
237
  connectedCallback() {
231
238
  super.connectedCallback();
@@ -2,6 +2,7 @@ import Layer from './layer.js';
2
2
  import VectorLayer from 'ol/layer/Vector.js';
3
3
  export default class LayerDrawing extends Layer {
4
4
  _oLayer: VectorLayer;
5
+ removalRequested: boolean;
5
6
  constructor(name: string, oLayer: VectorLayer);
6
7
  clone(): LayerDrawing;
7
8
  }
@@ -9,9 +9,10 @@ import Layer from './layer.js';
9
9
  */
10
10
  export default class LayerDrawing extends Layer {
11
11
  _oLayer;
12
+ removalRequested = false;
12
13
  constructor(name, oLayer) {
13
14
  super(0, name, 0, { isDefaultChecked: true });
14
- this.isRemovable = false;
15
+ this.isRemovable = true;
15
16
  this.isSwipeable = false;
16
17
  this.isDraggable = false;
17
18
  this._oLayer = oLayer;
package/package.json CHANGED
@@ -5,7 +5,7 @@
5
5
  "name": "GeoGirafe PSC",
6
6
  "url": "https://doc.geogirafe.org"
7
7
  },
8
- "version": "1.2.0-dev.2750042516",
8
+ "version": "1.2.0-dev.2751114005",
9
9
  "type": "module",
10
10
  "engines": {
11
11
  "node": ">=20.19.0"
@@ -1 +1 @@
1
- {"version":"1.2.0-dev.2750042516", "build":"2750042516", "date":"11/08/2026"}
1
+ {"version":"1.2.0-dev.2751114005", "build":"2751114005", "date":"11/08/2026"}
@@ -9,12 +9,19 @@ export default class CesiumDrawing {
9
9
  private handler;
10
10
  private entities;
11
11
  private fixedLineLength;
12
+ private drawingText;
12
13
  private readonly context;
13
14
  private get state();
14
15
  private get drawingState();
15
16
  private get config();
16
17
  constructor(map: MapComponent, stateLocation: string, context: IGirafeContext);
17
18
  setFixedLineLength(length: number): void;
19
+ setDrawingText(text: string): void;
20
+ /**
21
+ * A text shape displays the text entered in the tool parameters instead of its generated default name.
22
+ * If no text has been entered, the default name is kept so that the shape stays visible on the globe.
23
+ */
24
+ private getTextOf;
18
25
  private activateTool;
19
26
  private deactivateTool;
20
27
  private pickOnGlobe;
@@ -15,6 +15,7 @@ export default class CesiumDrawing {
15
15
  handler = undefined;
16
16
  entities = undefined;
17
17
  fixedLineLength = 0;
18
+ drawingText = '';
18
19
  context;
19
20
  get state() {
20
21
  return this.context.stateManager.state;
@@ -40,6 +41,16 @@ export default class CesiumDrawing {
40
41
  setFixedLineLength(length) {
41
42
  this.fixedLineLength = Number.isNaN(length) ? 0 : length;
42
43
  }
44
+ setDrawingText(text) {
45
+ this.drawingText = text;
46
+ }
47
+ /**
48
+ * A text shape displays the text entered in the tool parameters instead of its generated default name.
49
+ * If no text has been entered, the default name is kept so that the shape stays visible on the globe.
50
+ */
51
+ getTextOf(feature) {
52
+ return this.drawingText.trim().length > 0 ? this.drawingText : feature.name;
53
+ }
43
54
  activateTool(tool) {
44
55
  if (!this.canExecute('globe.draw')) {
45
56
  return;
@@ -83,6 +94,9 @@ export default class CesiumDrawing {
83
94
  const newCesiumEntities = this.getShapes(tool, newCesiumEntityPoints, new DrawingFeature(tool, this.drawingState, this.config.drawing));
84
95
  this.activeShapes.forEach((e) => this.entities.remove(e));
85
96
  const newFeature = new DrawingFeature(tool, this.drawingState, this.config.drawing);
97
+ if (tool === DrawingShape.Text) {
98
+ newFeature.name = this.getTextOf(newFeature);
99
+ }
86
100
  // The following code will be useful when we will remove OLCesium
87
101
  /*newCesiumEntities.forEach((e) => this.entities!.add(e));
88
102
  const updateStyle = (feature:DrawingFeature) => {
@@ -164,7 +178,7 @@ export default class CesiumDrawing {
164
178
  this.activeShapes.forEach((e) => this.entities.add(e));
165
179
  }
166
180
  // Tools that automatically terminate the shape after a fixed number of points
167
- if ((tool === DrawingShape.Point && this.activeShapePoints.length === 2) ||
181
+ if (((tool === DrawingShape.Point || tool === DrawingShape.Text) && this.activeShapePoints.length === 2) ||
168
182
  (tool === DrawingShape.Disk && this.activeShapePoints.length === 3) ||
169
183
  (tool === DrawingShape.Square && this.activeShapePoints.length === 3) ||
170
184
  (tool === DrawingShape.Rectangle && this.activeShapePoints.length === 3)) {
@@ -221,6 +235,15 @@ export default class CesiumDrawing {
221
235
  label: this.createLabel(feature.getCoordText(this.getPosition(pos[0])), font)
222
236
  })
223
237
  ];
238
+ case DrawingShape.Text:
239
+ return [
240
+ new Entity({
241
+ position: pos[0],
242
+ // The anchor point is needed to export the shape as a KML placemark
243
+ point: this.createPoint(strokeColor),
244
+ label: this.createLabel(this.getTextOf(feature), font)
245
+ })
246
+ ];
224
247
  case DrawingShape.Polyline:
225
248
  return [
226
249
  new Entity({ polyline: this.getPolyline(pos, feature) }),
@@ -12,7 +12,8 @@ export declare enum DrawingShape {
12
12
  Rectangle = 4,
13
13
  Disk = 5,
14
14
  FreehandPolyline = 6,
15
- FreehandPolygon = 7
15
+ FreehandPolygon = 7,
16
+ Text = 8
16
17
  }
17
18
  export type ArrowStyle = 'none' | 'start' | 'end' | 'both';
18
19
  export type ArrowPosition = 'whole' | 'each' | 'mid';
@@ -100,7 +101,7 @@ export default class DrawingFeature {
100
101
  getAreaText(area: number): string;
101
102
  getCoordText(coord: number[]): string;
102
103
  getAzimuthText(circle: CircleGeom): string;
103
- isPointOrPolyline(): boolean;
104
+ isPointPolylineOrText(): boolean;
104
105
  getVertexStyle(activeNode?: boolean): Style;
105
106
  static deserialize(serializedFeature: SerializedFeature, context: IGirafeContext, drawingState: DrawingState): DrawingFeature;
106
107
  static circleToPolygon(center: number[], radius: number, nbEdges?: number): number[][];
@@ -8,6 +8,7 @@ import GeoJSON from 'ol/format/GeoJSON.js';
8
8
  import { getAreaAsMetricText, getAzimuthAsText, getLengthAsMetricText } from '../utils/olutils.js';
9
9
  import { fromCircle } from 'ol/geom/Polygon.js';
10
10
  import LineString from 'ol/geom/LineString.js';
11
+ // New shapes must be appended at the end, as the numeric values are used to serialize features.
11
12
  export var DrawingShape;
12
13
  (function (DrawingShape) {
13
14
  DrawingShape[DrawingShape["Point"] = 0] = "Point";
@@ -18,6 +19,7 @@ export var DrawingShape;
18
19
  DrawingShape[DrawingShape["Disk"] = 5] = "Disk";
19
20
  DrawingShape[DrawingShape["FreehandPolyline"] = 6] = "FreehandPolyline";
20
21
  DrawingShape[DrawingShape["FreehandPolygon"] = 7] = "FreehandPolygon";
22
+ DrawingShape[DrawingShape["Text"] = 8] = "Text";
21
23
  })(DrawingShape || (DrawingShape = {}));
22
24
  export default class DrawingFeature {
23
25
  static geoJSONFormat = new GeoJSON();
@@ -206,10 +208,11 @@ export default class DrawingFeature {
206
208
  getAzimuthText(circle) {
207
209
  return getAzimuthAsText(this.measureInformation === 'none' ? undefined : (circle.getProperties()['azimuth'] ?? undefined));
208
210
  }
209
- isPointOrPolyline() {
211
+ isPointPolylineOrText() {
210
212
  return (this.type === DrawingShape.Point ||
211
213
  this.type === DrawingShape.Polyline ||
212
- this.type === DrawingShape.FreehandPolyline);
214
+ this.type === DrawingShape.FreehandPolyline ||
215
+ this.type === DrawingShape.Text);
213
216
  }
214
217
  getVertexStyle(activeNode = false) {
215
218
  const defaultConfig = this.defaultDrawingConfig;
@@ -21,12 +21,16 @@ export default class OlDrawing {
21
21
  private fixedSquareSide;
22
22
  private fixedRectangleWidth;
23
23
  private fixedRectangleHeight;
24
+ private drawingText;
24
25
  readonly drawingSource: VectorSource;
25
26
  readonly drawingLayer: VectorLayer;
26
27
  private lastClosestFeature;
27
28
  private translate;
28
29
  private transform;
29
30
  private readonly modifyFeatureChangeListeners;
31
+ private hoverListener;
32
+ private selectListener;
33
+ private hoveredTextFeatureId;
30
34
  defaultStyle: StyleFunction | undefined;
31
35
  private readonly updateGeometryInState;
32
36
  private get state();
@@ -46,6 +50,22 @@ export default class OlDrawing {
46
50
  private addEditContextMenu;
47
51
  private addEditInteractions;
48
52
  private removeEditInteractions;
53
+ private addHoverListener;
54
+ private removeHoverListener;
55
+ private addSelectListener;
56
+ private removeSelectListener;
57
+ /**
58
+ * Lets the user select the shapes to edit directly in the map, as an alternative to the drawing list:
59
+ * a click selects the shape under the cursor exclusively, a click with the platform modifier key (ctrl / meta)
60
+ * adds the shape to the current selection or removes it from it. A click outside of any shape clears the selection.
61
+ */
62
+ private selectClickedFeature;
63
+ /**
64
+ * Only writes to the state if the selection of the shape really changed, so that the map and the drawing
65
+ * panel are not refreshed for the shapes that are not concerned by the click.
66
+ */
67
+ private setFeatureSelected;
68
+ private findFeatureIdAtPixel;
49
69
  private readonly prepareMenuEntriesForVertex;
50
70
  private readonly prepareMenuEntriesForShape;
51
71
  /**
@@ -98,6 +118,12 @@ export default class OlDrawing {
98
118
  setFixedSquareSide(length: number): void;
99
119
  setFixedRectangleWidth(width: number): void;
100
120
  setFixedRectangleHeight(height: number): void;
121
+ setDrawingText(text: string): void;
122
+ /**
123
+ * A text shape displays the text entered in the tool parameters instead of its generated default name.
124
+ * If no text has been entered, the default name is kept so that the shape stays visible on the map.
125
+ */
126
+ private applyDrawingText;
101
127
  private createLineStringFixedLength;
102
128
  private createSquareFixedLength;
103
129
  private createPolygonFixedLength;
@@ -106,11 +132,24 @@ export default class OlDrawing {
106
132
  private addDrawInteraction;
107
133
  centerViewOnFeature(drawingFeature: DrawingFeature): void;
108
134
  private getStyle;
135
+ /**
136
+ * Vertices are rendered on the shapes selected for editing. A text shape is an exception: as its geometry is
137
+ * a single anchor point hidden behind the text, its vertex is only revealed while the cursor hovers the text.
138
+ */
139
+ private shouldRenderVertices;
109
140
  private removeDrawInteraction;
110
141
  private removeModifyInteraction;
111
142
  private getDrawingShapeType;
112
143
  private isSquareOrRectangle;
144
+ private isSquareRectangleOrText;
113
145
  private shouldAllowVertexInsertionAtEvent;
146
+ /**
147
+ * A text shape is only made of its text: its anchor point is hidden behind it. To let the user know where to
148
+ * grab the shape, the anchor vertex is revealed as long as the cursor hovers the text.
149
+ */
150
+ private updateHoveredTextFeature;
151
+ private setHoveredTextFeature;
152
+ private findHoveredTextFeatureIdAtPixel;
114
153
  private getHoveredModifiableFeatureAtPixel;
115
154
  private applyScaleRotateOnModifyStyle;
116
155
  private clearModifyFeatureChangeListeners;
@@ -119,5 +158,7 @@ export default class OlDrawing {
119
158
  registerInteractions(): void;
120
159
  unregisterInteractions(): void;
121
160
  private canExecute;
161
+ private disableContextMenu;
162
+ private enableContextMenu;
122
163
  private fixLastLength;
123
164
  }
@@ -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) {