@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
@@ -0,0 +1,121 @@
1
+ import { html as uHtml } from 'uhtml';
2
+ import GirafeHTMLElement from '../../base/GirafeHTMLElement.js';
3
+ import FacebookLogo from '../../assets/icons/facebook.svg';
4
+ import TwitterLogo from '../../assets/icons/twitter.svg';
5
+ import LinkedInLogo from '../../assets/icons/linkedin.svg';
6
+ import MailLogo from '../../assets/icons/share-mail.svg';
7
+ import ShareNativeLogo from '../../assets/icons/share-native.svg';
8
+ async function shareUsingNavigator(url, title) {
9
+ const shareData = {
10
+ title: title ?? document.title,
11
+ text: 'Schau dir diese Seite an:',
12
+ url: url
13
+ };
14
+ if (!navigator.share) {
15
+ await fallbackShare(shareData.url ?? window.location.href);
16
+ return;
17
+ }
18
+ try {
19
+ await navigator.share(shareData);
20
+ console.log('Inhalt wurde geteilt.');
21
+ }
22
+ catch (error) {
23
+ if (error instanceof DOMException && error.name === 'AbortError') {
24
+ // Benutzer hat den Share-Dialog geschlossen.
25
+ return;
26
+ }
27
+ console.error('Teilen fehlgeschlagen:', error);
28
+ }
29
+ }
30
+ async function fallbackShare(url) {
31
+ try {
32
+ await navigator.clipboard.writeText(url);
33
+ alert('Der Link wurde in die Zwischenablage kopiert.');
34
+ }
35
+ catch {
36
+ // Einfachster Fallback für ältere Browser.
37
+ window.prompt('Link kopieren:', url);
38
+ }
39
+ }
40
+ class ShareMobileComponent extends GirafeHTMLElement {
41
+ templateUrl = null;
42
+ styleUrls = null;
43
+ template = () => {
44
+ return uHtml `<style>
45
+ .hidden{display:none!important}.gg-rotate90{transform:rotate(90deg)}.gg-rotate180{transform:rotate(180deg)}.gg-rotate270{transform:rotate(270deg)}img{filter:var(--svg-filter)}img.legend-image{filter:var(--svg-map-filter);background:var(--svg-legend-bkg)}div{scrollbar-width:thin}a,a:visited{color:var(--link-color)}@keyframes spin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}@keyframes spin-wait{0%{transform:rotate(0)}7%{transform:rotate(360deg)}to{transform:rotate(360deg)}}.gg-spin{animation-name:spin;animation-duration:2s;animation-timing-function:linear;animation-iteration-count:infinite}.gg-spin-wait{animation-name:spin-wait;animation-duration:10s;animation-timing-function:linear;animation-iteration-count:infinite}::-webkit-scrollbar{width:5px}::-webkit-scrollbar-thumb{background:#999}button,input,select,textarea{font:inherit}.gg-button,.gg-select,.gg-input,.gg-textarea{background-color:var(--bkg-color);color:var(--text-color);border:var(--app-standard-border);box-sizing:border-box;cursor:pointer;border-radius:3px;outline:0;margin:0;padding:0 0 0 .5rem;display:inline-block}.gg-label{background-color:var(--bkg-color);color:var(--text-color);border:none;align-items:center;margin:0;padding:0;display:flex}.gg-button,.gg-select,.gg-input,.gg-label{min-height:calc(var(--app-standard-height) / 1.5)}.gg-textarea{max-height:initial;resize:vertical;height:6rem;padding:.5rem;line-height:1.3rem}.gg-input{cursor:text}.gg-checkbox{accent-color:var(--text-color);width:1.2rem}.gg-range{accent-color:var(--text-color)}.gg-button{padding:0 .5rem}.gg-button.active{border:solid 1px var(--text-color-grad1);background-color:var(--bkg-color-grad2)}.gg-button:disabled{background-color:var(--bkg-color-grad1);color:var(--text-color-grad2);cursor:not-allowed;border:none}.gg-button:disabled img{filter:opacity(.6)}.gg-input:disabled,.gg-select:disabled,.gg-textarea:disabled{background-color:var(--bkg-color-grad1);color:var(--text-color-grad2);cursor:not-allowed}.gg-button>img{vertical-align:middle}.gg-icon-button{color:var(--text-color);cursor:pointer;background-color:#0000;border:none;flex-direction:column;justify-content:center;align-items:center;padding:0;display:flex}.gg-icon{justify-content:center;align-items:center;display:flex}.gg-big,.gg-big-withtext{min-width:var(--app-standard-height);min-height:var(--app-standard-height);max-height:var(--app-standard-height)}.gg-big img,.gg-big-withtext img{width:calc(var(--app-standard-height) - 1.5rem);margin:0}.gg-big-withtext span{font-variant:small-caps;padding:0 1rem;font-size:.9rem}.gg-medium,.gg-medium-withtext{min-width:calc(var(--app-standard-height) / 1.2);min-height:calc(var(--app-standard-height) / 1.2);max-height:calc(var(--app-standard-height) / 1.2);flex-direction:row}.gg-medium img{width:calc(var(--app-standard-height) / 2.4);margin:0}.gg-medium-withtext img{width:calc(var(--app-standard-height) / 2.4);margin-left:.5rem}.gg-medium-withtext span{padding:0 1rem 0 .5rem;font-size:.9rem}.gg-small,.gg-small-withtext{min-width:calc(var(--app-standard-height) / 2);min-height:calc(var(--app-standard-height) / 2);max-height:calc(var(--app-standard-height) / 2);flex-direction:row}.gg-small img{width:calc(var(--app-standard-height) / 3);margin:0}.gg-small-withtext img{width:calc(var(--app-standard-height) / 3);margin-left:.5rem}.gg-small-withtext span{padding:0 .5rem 0 .3rem;font-size:.9rem}.gg-button:hover:not(:disabled),.gg-select:hover:not(:disabled),.gg-input:hover:not(:disabled),.gg-textarea:hover:not(:disabled),.gg-icon-button:hover:not(:disabled){background-color:var(--bkg-color-grad1)}.gg-opacity{opacity:.5}.gg-opacity:hover{opacity:1;background-color:#0000}.gg-tabs{cursor:pointer;grid-auto-flow:column;padding-bottom:1rem;font-size:1rem;display:grid}.gg-tab{border:none;border-bottom:var(--app-standard-border);cursor:pointer;color:var(--text-color);background:0 0;padding:.5rem}.gg-tab.active{border-bottom:solid 1px var(--text-color)}.girafe-button-big,.girafe-button-large,.girafe-button-small,.girafe-button-tiny{color:var(--text-color);background-color:#0000;border:none;flex-direction:column;display:flex}.girafe-button-big:hover,.girafe-button-large:hover,.girafe-button-small:hover,.girafe-button-tiny:hover{background-color:var(--bkg-color-grad1);cursor:pointer}.girafe-button-big.dark,.girafe-button-large.dark,.girafe-button-small.dark,.girafe-button-tiny.dark{background-color:var(--bkg-color);filter:invert()}.girafe-button-big{width:var(--app-standard-height);height:var(--app-standard-height);align-items:center;padding:1rem}.girafe-button-big img{overflow:hidden}.girafe-button-large{flex-direction:row}.girafe-button-large img{height:2rem;margin:.3rem}.girafe-button-large span{height:2rem;margin:.3rem;line-height:2rem}.girafe-button-small{min-width:calc(var(--app-standard-height) / 2);height:calc(var(--app-standard-height) / 2);align-items:center;padding:.5rem}.girafe-button-small img{overflow:hidden}.girafe-button-small span{text-align:left;text-overflow:ellipsis;width:100%;overflow:hidden}.girafe-button-tiny{align-items:center;width:1rem;height:1rem;padding:0}.girafe-button-tiny img{overflow:hidden}.girafe-onboarding-theme{background-color:var(--bkg-color)!important;color:var(--text-color)!important}.girafe-onboarding-theme button{background-color:var(--bkg-color)!important;color:var(--text-color)!important;text-shadow:none!important}.girafe-onboarding-theme button.driver-popover-close-btn{z-index:10000}.tippy-box{background-color:var(--bkg-color)!important;border:var(--app-standard-border)!important}.tippy-content{color:var(--text-color)!important}.tippy-arrow{color:var(--bkg-color)!important}.tippy-box[data-theme~=error]{background-color:var(--error-color);white-space:pre-line}div.tippy-box[data-theme=image-popup]{border:var(--app-standard-border)!important;& div.tippy-content{background-color:var(--bkg-color)!important;& img{max-width:20rem;max-height:20rem}}& div.tippy-arrow{color:var(--bkg-color)!important}}
46
+ </style><style>
47
+ #container{width:calc(100% - 50px);margin:25px}#title{color:#3f68a7;text-transform:uppercase;text-align:center;margin:.5em 0;font-size:1.8em;font-weight:600}.qrcode-container{flex-direction:column;align-items:center;display:flex}.qrcode{width:calc(100% - 50px - 2rem);display:block}.qrcode-placeholder{opacity:.7;content:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII=);width:calc(100% - 50px - 2rem);height:calc(100% - 50px - 2rem);display:block}.actions-container{flex-direction:row;flex-grow:1;justify-content:center;column-gap:.5rem;width:stretch;height:3.25rem;margin-top:1rem;display:flex;& button{filter:drop-shadow(0 4px 7px #0004382c);background-color:#fff;border-radius:4px;flex-direction:column;padding:.25rem;display:flex;& img{width:2.5rem;height:2.5rem}}}
48
+ </style>
49
+ <style>${this.customStyle}</style>
50
+ <div id="container" style="${this.state.interface.swipeupPanelContent === 'share' ? '' : 'display: none;'}"><div id="title" i18n="Share this map">Share this map</div><div id="share-map" class="${this.urlShortener ? 'group' : 'hidden'}"><div class="${this.awaitingShareLink ? 'disabled qrcode-container' : 'enabled qrcode-container'}"><img alt="share-qrcode" class="${(this.qrCode ? 'qrcode' : 'qrcode-placeholder')}" src="${this.qrCode}"><div class="${(this.success ? 'hidden' : 'qrcode error')}"><img alt="error-icon" src="icons/error.svg"> <span i18n="Error, can not fetch the shortener service. Please try again later.">Error, can not fetch the shortener service. Please try again later.</span></div><div class="${this.awaitingShareLink ? 'disabled actions-container' : 'enabled actions-container'}"><button onclick="${() => this.shareFacebook()}"><img alt="share-facebook" src="${this.facebookLogo}"></button> <button onclick="${() => this.shareTwitter()}"><img alt="share-twitter" src="${this.twitterLogo}"></button> <button onclick="${() => this.shareLinkedIn()}"><img alt="share-linkedin" src="${this.linkedInLogo}"></button> <button onclick="${() => this.shareMail()}"><img alt="share-mail" src="${this.mailLogo}"></button> <button onclick="${() => this.shareNative()}"><img alt="share-native" src="${this.shareNativeLogo}"></button></div></div></div></div>
51
+ ${this.htmlUnsafe(this.feedbackTemplateHtml ?? '')}`;
52
+ };
53
+ shareLink;
54
+ qrCode;
55
+ success = true;
56
+ twitterLogo = TwitterLogo;
57
+ facebookLogo = FacebookLogo;
58
+ linkedInLogo = LinkedInLogo;
59
+ mailLogo = MailLogo;
60
+ shareNativeLogo = ShareNativeLogo;
61
+ awaitingShareLink = false;
62
+ get urlShortener() {
63
+ return this.context.shareManager.UrlShortener;
64
+ }
65
+ constructor() {
66
+ super('share-mobile');
67
+ }
68
+ render() {
69
+ super.render();
70
+ }
71
+ connectedCallback() {
72
+ super.connectedCallback();
73
+ this.render();
74
+ super.girafeTranslate();
75
+ this.registerEvents();
76
+ // Generate the share link when the component is rendered (but don't copy to clipboard)
77
+ this.generateShareLink().then(() => this.refreshRender());
78
+ }
79
+ registerEvents() {
80
+ this.subscribe('interface.swipeupPanelContent', () => {
81
+ this.render();
82
+ });
83
+ this.subscribe('position', () => {
84
+ super.render();
85
+ });
86
+ }
87
+ async generateShareLink() {
88
+ if (!this.urlShortener) {
89
+ return;
90
+ }
91
+ this.awaitingShareLink = true;
92
+ this.refreshRender();
93
+ const baseUrl = this.context.urlManager.getBaseUrlPath();
94
+ const hash = this.context.shareManager.getStateToShare();
95
+ // Get short URL
96
+ const longurl = `${baseUrl}#${hash}`;
97
+ const response = await this.urlShortener.shortenUrl(longurl);
98
+ this.shareLink = response.shorturl;
99
+ this.success = response.success;
100
+ this.qrCode = response.qrcode;
101
+ this.awaitingShareLink = false;
102
+ }
103
+ shareFacebook() {
104
+ window.open('https://www.facebook.com/sharer/sharer.php?u=' + this.shareLink, '_blank');
105
+ }
106
+ shareTwitter() {
107
+ window.open('https://twitter.com/intent/tweet?url=' + this.shareLink, '_blank');
108
+ }
109
+ shareLinkedIn() {
110
+ window.open('https://www.linkedin.com/shareArticle?url=' + encodeURIComponent(this.shareLink), '_blank');
111
+ }
112
+ shareMail() {
113
+ const subject = encodeURIComponent('Check out this link');
114
+ const body = encodeURIComponent('I thought you might be interested in this link: ' + this.shareLink);
115
+ window.location.href = 'mailto:?subject=' + subject + '&body=' + body;
116
+ }
117
+ async shareNative() {
118
+ await shareUsingNavigator(this.shareLink, 'Geogirafe');
119
+ }
120
+ }
121
+ export default ShareMobileComponent;
@@ -0,0 +1,56 @@
1
+ /* SPDX-License-Identifier: Apache-2.0 */
2
+ #container {
3
+ width: calc(100% - 50px);
4
+ margin: 25px;
5
+ }
6
+
7
+ #title {
8
+ color: #3f68a7;
9
+ text-transform: uppercase;
10
+ text-align: center;
11
+ margin: 0.5em 0;
12
+ font-size: 1.8em;
13
+ font-weight: 600;
14
+ }
15
+
16
+ .qrcode-container {
17
+ display: flex;
18
+ align-items: center;
19
+ flex-direction: column;
20
+ }
21
+
22
+ .qrcode {
23
+ display: block;
24
+ width: calc(100% - 50px - 2rem);
25
+ }
26
+ .qrcode-placeholder {
27
+ display: block;
28
+ width: calc(100% - 50px - 2rem);
29
+ height: calc(100% - 50px - 2rem);
30
+ opacity: 0.7;
31
+ content: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII=);
32
+ }
33
+
34
+ .actions-container {
35
+ display: flex;
36
+ flex-direction: row;
37
+ flex-grow: 1;
38
+ justify-content: center;
39
+ width: stretch;
40
+ column-gap: 0.5rem;
41
+ margin-top: 1rem;
42
+ height: 3.25rem;
43
+
44
+ button {
45
+ background-color: white;
46
+ border-radius: 4px;
47
+ filter: drop-shadow(0px 4px 7px #0004382c);
48
+ display: flex;
49
+ flex-direction: column;
50
+ padding: 0.25rem;
51
+ img {
52
+ width: 2.5rem;
53
+ height: 2.5rem;
54
+ }
55
+ }
56
+ }
@@ -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.2736532702",
8
+ "version": "1.2.0-dev.2751114005",
9
9
  "type": "module",
10
10
  "engines": {
11
11
  "node": ">=20.19.0"
@@ -106,9 +106,11 @@
106
106
  <girafe-menu-mobile-offline-button slot="mobile-menu-content"></girafe-menu-mobile-offline-button>
107
107
  <girafe-menu-mobile-drawing-button slot="mobile-menu-content"></girafe-menu-mobile-drawing-button>
108
108
  <girafe-oauth-mobile slot="mobile-menu-content"></girafe-oauth-mobile>
109
+ <girafe-menu-mobile-share-button slot="mobile-menu-content"></girafe-menu-mobile-share-button>
109
110
  </girafe-menu-mobile>
110
111
 
111
112
  <girafe-offline slot="main"></girafe-offline>
113
+ <girafe-share-mobile slot="main"></girafe-share-mobile>
112
114
  </girafe-swipe-up-panel>
113
115
  </girafe-app>
114
116
  </body>
@@ -1 +1 @@
1
- {"version":"1.2.0-dev.2736532702", "build":"2736532702", "date":"06/08/2026"}
1
+ {"version":"1.2.0-dev.2751114005", "build":"2751114005", "date":"11/08/2026"}
@@ -24,6 +24,8 @@ import MobileGroupElementComponent from '../../components/themes-mobile/group/co
24
24
  import MobileLayerElementComponent from '../../components/themes-mobile/layer/component.js';
25
25
  import MobileSelectedLayerElementComponent from '../../components/themes-mobile/layer-selected/component.js';
26
26
  import GetDirectionsArtifact from '../../components/getdirections/component.js';
27
+ import MenuMobileShareButton from '../../components/menu-buttons-mobile/MenuMobileShareButton.js';
28
+ import ShareMobileComponent from '../../components/share-mobile/component.js';
27
29
  export default class GeoGirafeAppMobile extends GeoGirafeApp {
28
30
  initializeInterface() {
29
31
  if (window.cordova) {
@@ -57,6 +59,7 @@ export default class GeoGirafeAppMobile extends GeoGirafeApp {
57
59
  customElements.define('girafe-drawing-mobile', DrawingComponentMobile);
58
60
  customElements.define('girafe-drawing-container-mobile', DrawingContainerMobile);
59
61
  customElements.define('girafe-menu-mobile-3d-button', MenuMobile3dButton);
62
+ customElements.define('girafe-menu-mobile-share-button', MenuMobileShareButton);
60
63
  customElements.define('girafe-menu-mobile-offline-button', MenuMobileOfflineButton);
61
64
  customElements.define('girafe-menu-mobile-drawing-button', MenuMobileDrawingButton);
62
65
  customElements.define('girafe-oauth-mobile', MobileOauthComponent);
@@ -65,5 +68,6 @@ export default class GeoGirafeAppMobile extends GeoGirafeApp {
65
68
  customElements.define('girafe-layer', MobileLayerElementComponent);
66
69
  customElements.define('girafe-layer-selected', MobileSelectedLayerElementComponent);
67
70
  customElements.define('girafe-get-directions', GetDirectionsArtifact);
71
+ customElements.define('girafe-share-mobile', ShareMobileComponent);
68
72
  }
69
73
  }
@@ -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
  }