@geogirafe/lib-geoportal 1.1.0-dev.2625220907 → 1.1.0-dev.2629006744

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/api/apicontext.js CHANGED
@@ -33,6 +33,7 @@ import OnBoardingManager from '../tools/onboarding/onboardingmanager.js';
33
33
  import ThemeFavoritesManager from '../tools/themes/themefavoritesmanager.js';
34
34
  import SearchManager from '../tools/search/searchmanager.js';
35
35
  import FeedbackManager from '../tools/feedback/feedbackmanager.js';
36
+ import { updateConfigPaths } from './apiutils.js';
36
37
  export default class GirafeApiContext {
37
38
  userDataManager;
38
39
  configManager;
@@ -110,9 +111,13 @@ export default class GirafeApiContext {
110
111
  this.userDataManager.initializeSingleton();
111
112
  this.configManager.initializeSingleton();
112
113
  await this.configManager.loadConfig();
114
+ // For the API context, some configuraton options in the config file can be relative.
115
+ // We have to convert them to absolute paths.
116
+ updateConfigPaths(this.configManager.Config);
113
117
  this.logManager.initializeSingleton();
114
118
  this.logManager.initLogging();
115
119
  this.stateManager.initializeSingleton();
120
+ this.stateManager.state.interface.isApi = true;
116
121
  this.orderingManager.initializeSingleton();
117
122
  this.applicationLifeCycleManager.initializeSingleton();
118
123
  this.mapManager.initializeSingleton();
@@ -0,0 +1,3 @@
1
+ import GirafeConfig from '../tools/configuration/girafeconfig.js';
2
+ export declare function updateConfigPaths(config: GirafeConfig): void;
3
+ export declare function updateRessourcesPaths(shadow: ShadowRoot): void;
@@ -0,0 +1,61 @@
1
+ export function updateConfigPaths(config) {
2
+ const baseApiUrl = getBaseApiUrl();
3
+ for (const translation of Object.values(config.languages.translations)) {
4
+ for (let i = 0; i < translation.length; ++i) {
5
+ const filepath = translation[i];
6
+ if (isRelativePath(filepath)) {
7
+ const absolutePath = new URL(filepath, baseApiUrl).href;
8
+ translation[i] = absolutePath;
9
+ }
10
+ }
11
+ }
12
+ }
13
+ export function updateRessourcesPaths(shadow) {
14
+ const baseApiUrl = getBaseApiUrl();
15
+ updateElements(shadow, baseApiUrl, 'img', 'src');
16
+ updateElements(shadow, baseApiUrl, 'link', 'href');
17
+ }
18
+ function updateElements(shadow, baseApiUrl, elementTagname, attributeName) {
19
+ const elements = shadow.querySelectorAll(`${elementTagname}[${attributeName}]`);
20
+ for (const element of elements) {
21
+ const path = element.getAttribute(attributeName);
22
+ if (path && isRelativePath(path)) {
23
+ const absolutePath = new URL(path, baseApiUrl).href;
24
+ element.setAttribute(attributeName, absolutePath);
25
+ }
26
+ }
27
+ }
28
+ function getBaseApiUrl() {
29
+ let baseApiUrl = import.meta.url;
30
+ // Example when imported locally in dev mode: 'https://app.localhost:8080/src/base/GirafeHTMLElement.ts?t=1782368741013'
31
+ // Example when imported from the published application: 'https://demo.geogirafe.org/mapbs/assets/component-UojAUdda.js'
32
+ // Exemple when imported as an api: https://app.localhost:4173/assets/component-UojAUdda.js
33
+ let index = baseApiUrl.indexOf('/src/');
34
+ if (index <= 0) {
35
+ index = baseApiUrl.indexOf('/assets/');
36
+ }
37
+ if (index > 0) {
38
+ baseApiUrl = baseApiUrl.substring(0, index + 1);
39
+ return baseApiUrl;
40
+ }
41
+ else {
42
+ throw new Error('API: Cannot calculate the baseUrl for importing assets.');
43
+ }
44
+ }
45
+ function isRelativePath(path) {
46
+ if (path.startsWith('http:'))
47
+ return false;
48
+ if (path.startsWith('https:'))
49
+ return false;
50
+ if (path.startsWith('data:'))
51
+ return false;
52
+ if (path.startsWith('blob:'))
53
+ return false;
54
+ if (path.startsWith('javascript:'))
55
+ return false;
56
+ if (path.startsWith('mailto:'))
57
+ return false;
58
+ if (path.startsWith('#'))
59
+ return false;
60
+ return true;
61
+ }
package/api/main.d.ts CHANGED
@@ -1,3 +1,4 @@
1
1
  export { default as GirafeApiContext } from './apicontext.js';
2
2
  export { default as GeoGirafeApi } from './apigeogirafeapp.js';
3
3
  export { default as ApiSessionManager } from './apisessionmanager.js';
4
+ export { updateConfigPaths, updateRessourcesPaths } from './apiutils.js';
package/api/main.js CHANGED
@@ -1,3 +1,4 @@
1
1
  export { default as GirafeApiContext } from './apicontext.js';
2
2
  export { default as GeoGirafeApi } from './apigeogirafeapp.js';
3
3
  export { default as ApiSessionManager } from './apisessionmanager.js';
4
+ export { updateConfigPaths, updateRessourcesPaths } from './apiutils.js';
@@ -54,6 +54,7 @@ declare abstract class GirafeHTMLElement extends HTMLElement {
54
54
  * has already been rendered and needs to be updated.
55
55
  */
56
56
  protected refreshRender(): void;
57
+ private renderInternal;
57
58
  /**
58
59
  * Renders a hidden span with the name of the component.
59
60
  * Useful to render a placeholder for not visible component.
@@ -1,5 +1,6 @@
1
1
  // SPDX-License-Identifier: Apache-2.0
2
2
  import { render as uRender, html as uHtml } from 'uhtml';
3
+ import { updateRessourcesPaths } from '../api/apiutils.js';
3
4
  class GirafeHTMLElement extends HTMLElement {
4
5
  template;
5
6
  name;
@@ -92,7 +93,7 @@ class GirafeHTMLElement extends HTMLElement {
92
93
  if (this.template) {
93
94
  this.defineDisplayStyle();
94
95
  this.show();
95
- uRender(this.shadow, this.template);
96
+ this.renderInternal();
96
97
  this.rendered = true;
97
98
  this.userInfoChanged();
98
99
  }
@@ -112,9 +113,7 @@ class GirafeHTMLElement extends HTMLElement {
112
113
  if (!this.rendered) {
113
114
  throw Error('Component cannot be re-rendered. Please call render() first.');
114
115
  }
115
- // Call to uRender MUST stay synchron (no timeout)
116
- // Otherwise, this cause unwanted effects like delay when updating templates
117
- uRender(this.shadow, this.template);
116
+ this.renderInternal();
118
117
  // Use a debouncing to prevent multiple execution of this method
119
118
  // If multiple refresh at the same time are called.
120
119
  if (this.timeoutId) {
@@ -125,6 +124,16 @@ class GirafeHTMLElement extends HTMLElement {
125
124
  this.userInfoChanged();
126
125
  });
127
126
  }
127
+ renderInternal() {
128
+ // Call to uRender MUST stay synchron (no timeout)
129
+ // Otherwise, this cause unwanted effects like delay when updating templates
130
+ uRender(this.shadow, this.template);
131
+ if (this.context.stateManager.state.interface.isApi) {
132
+ // For the API, we must ensure that URLs are not relative to the corent page
133
+ // But relative to the page where they are imported from (import.meta.url)
134
+ updateRessourcesPaths(this.shadow);
135
+ }
136
+ }
128
137
  /**
129
138
  * Renders a hidden span with the name of the component.
130
139
  * Useful to render a placeholder for not visible component.
@@ -102,7 +102,7 @@ async function readStyleCodeFromFile(styleFilePath) {
102
102
  styleCode = await minify.css(styleCode);
103
103
  return styleCode;
104
104
  } catch (error) {
105
- console.error(`Error reading style file for ${currentFilename}: ${error}`);
105
+ console.error(`Error reading style file for ${styleFilePath}: ${error}`);
106
106
  throw error;
107
107
  }
108
108
  }
@@ -49,12 +49,14 @@ export default class MapComponent extends GirafeHTMLElement {
49
49
  styleUrls = null;
50
50
  template = () => {
51
51
  return uHtml `<style>
52
+ :root,:host{--ol-background-color:white;--ol-accent-background-color:#f5f5f5;--ol-subtle-background-color:#80808040;--ol-partial-background-color:#ffffffbf;--ol-foreground-color:#333;--ol-subtle-foreground-color:#666;--ol-brand-color:#0af}.ol-box{box-sizing:border-box;border:1.5px solid var(--ol-background-color);background-color:var(--ol-partial-background-color);border-radius:2px}.ol-mouse-position{position:absolute;top:8px;right:8px}.ol-scale-line{background:var(--ol-partial-background-color);border-radius:4px;padding:2px;position:absolute;bottom:8px;left:8px}.ol-scale-line-inner{border:1px solid var(--ol-subtle-foreground-color);color:var(--ol-foreground-color);text-align:center;will-change:contents, width;border-top:none;margin:1px;font-size:10px;transition:all .25s}.ol-scale-bar{position:absolute;bottom:8px;left:8px}.ol-scale-bar-inner{display:flex}.ol-scale-step-marker{background-color:var(--ol-foreground-color);float:right;z-index:10;width:1px;height:15px}.ol-scale-step-text{z-index:11;color:var(--ol-foreground-color);text-shadow:-1.5px 0 var(--ol-partial-background-color), 0 1.5px var(--ol-partial-background-color), 1.5px 0 var(--ol-partial-background-color), 0 -1.5px var(--ol-partial-background-color);font-size:10px;position:absolute;bottom:-5px}.ol-scale-text{text-align:center;color:var(--ol-foreground-color);text-shadow:-1.5px 0 var(--ol-partial-background-color), 0 1.5px var(--ol-partial-background-color), 1.5px 0 var(--ol-partial-background-color), 0 -1.5px var(--ol-partial-background-color);font-size:12px;position:absolute;bottom:25px}.ol-scale-singlebar{z-index:9;box-sizing:border-box;border:1px solid var(--ol-foreground-color);height:10px;position:relative}.ol-scale-singlebar-even{background-color:var(--ol-subtle-foreground-color)}.ol-scale-singlebar-odd{background-color:var(--ol-background-color)}.ol-unsupported{display:none}.ol-viewport,.ol-unselectable{-webkit-touch-callout:none;-webkit-user-select:none;-moz-user-select:none;user-select:none;-webkit-tap-highlight-color:transparent}.ol-viewport canvas{all:unset;overflow:hidden}.ol-viewport{touch-action:pan-x pan-y}.ol-selectable{-webkit-touch-callout:default;-webkit-user-select:text;-moz-user-select:text;user-select:text}.ol-grabbing{cursor:-webkit-grabbing;cursor:-moz-grabbing;cursor:grabbing}.ol-grab{cursor:move;cursor:-webkit-grab;cursor:-moz-grab;cursor:grab}.ol-control{background-color:var(--ol-subtle-background-color);border-radius:4px;position:absolute}.ol-zoom{top:.5em;left:.5em}.ol-rotate{transition:opacity .25s linear,visibility linear;top:.5em;right:.5em}.ol-rotate.ol-hidden{opacity:0;visibility:hidden;transition:opacity .25s linear,visibility 0s linear .25s}.ol-zoom-extent{top:4.643em;left:.5em}.ol-full-screen{top:.5em;right:.5em}.ol-control button{color:var(--ol-subtle-foreground-color);font-weight:700;font-size:inherit;text-align:center;background-color:var(--ol-background-color);border:none;border-radius:2px;width:1.375em;height:1.375em;margin:1px;padding:0;line-height:.4em;text-decoration:none;display:block}.ol-control button::-moz-focus-inner{border:none;padding:0}.ol-zoom-extent button{line-height:1.4em}.ol-compass{will-change:transform;font-weight:400;display:block}.ol-touch .ol-control button{font-size:1.5em}.ol-touch .ol-zoom-extent{top:5.5em}.ol-control button:hover,.ol-control button:focus{outline:1px solid var(--ol-subtle-foreground-color);color:var(--ol-foreground-color);text-decoration:none}.ol-zoom .ol-zoom-in{border-radius:2px 2px 0 0}.ol-zoom .ol-zoom-out{border-radius:0 0 2px 2px}.ol-attribution{text-align:right;flex-flow:row-reverse;align-items:center;max-width:calc(100% - 1.3em);display:flex;bottom:.5em;right:.5em}.ol-attribution a{color:var(--ol-subtle-foreground-color);text-decoration:none}.ol-attribution ul{color:var(--ol-foreground-color);text-shadow:0 0 2px var(--ol-background-color);margin:0;padding:1px .5em;font-size:12px}.ol-attribution li{list-style:none;display:inline}.ol-attribution li:not(:last-child):after{content:" "}.ol-attribution img{max-height:2em;max-width:inherit;vertical-align:middle}.ol-attribution button{flex-shrink:0}.ol-attribution.ol-collapsed ul{display:none}.ol-attribution:not(.ol-collapsed){background:var(--ol-partial-background-color)}.ol-attribution.ol-uncollapsible{border-radius:4px 0 0;bottom:0;right:0}.ol-attribution.ol-uncollapsible img{max-height:1.6em;margin-top:-.2em}.ol-attribution.ol-uncollapsible button{display:none}.ol-zoomslider{height:200px;top:4.5em;left:.5em}.ol-zoomslider button{height:10px;position:relative}.ol-touch .ol-zoomslider{top:5.5em}.ol-overviewmap{bottom:.5em;left:.5em}.ol-overviewmap.ol-uncollapsible{border-radius:0 4px 0 0;bottom:0;left:0}.ol-overviewmap .ol-overviewmap-map,.ol-overviewmap button{display:block}.ol-overviewmap .ol-overviewmap-map{border:1px solid var(--ol-subtle-foreground-color);width:150px;height:150px}.ol-overviewmap:not(.ol-collapsed) button{position:absolute;bottom:0;left:0}.ol-overviewmap.ol-collapsed .ol-overviewmap-map,.ol-overviewmap.ol-uncollapsible button{display:none}.ol-overviewmap:not(.ol-collapsed){background:var(--ol-subtle-background-color)}.ol-overviewmap-box{border:1.5px dotted var(--ol-subtle-foreground-color)}.ol-overviewmap .ol-overviewmap-box:hover{cursor:move}.ol-overviewmap .ol-viewport:hover{cursor:pointer}
53
+ </style><style>
52
54
  *{font-family:Arial,sans-serif}.hidden{display:none!important}.gg-rotate90{transform:rotate(90deg)}.gg-rotate180{transform:rotate(180deg)}.gg-rotate270{transform:rotate(270deg)}img{filter:var(--svg-filter)}img.legend-image{filter:var(--svg-map-filter);background:var(--svg-legend-bkg)}div{scrollbar-width:thin}a,a:visited{color:var(--link-color)}@keyframes spin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}@keyframes spin-wait{0%{transform:rotate(0)}7%{transform:rotate(360deg)}to{transform:rotate(360deg)}}.gg-spin{animation-name:spin;animation-duration:2s;animation-timing-function:linear;animation-iteration-count:infinite}.gg-spin-wait{animation-name:spin-wait;animation-duration:10s;animation-timing-function:linear;animation-iteration-count:infinite}::-webkit-scrollbar{width:5px}::-webkit-scrollbar-thumb{background:#999}.gg-button,.gg-select,.gg-input,.gg-textarea{background-color:var(--bkg-color);color:var(--text-color);border:var(--app-standard-border);box-sizing:border-box;cursor:pointer;border-radius:3px;outline:0;margin:0;padding:0 0 0 .5rem;display:inline-block}.gg-label{background-color:var(--bkg-color);color:var(--text-color);border:none;align-items:center;margin:0;padding:0;display:flex}.gg-button,.gg-select,.gg-input,.gg-label{min-height:calc(var(--app-standard-height) / 1.5)}.gg-textarea{max-height:initial;resize:vertical;height:6rem;padding:.5rem;line-height:1.3rem}.gg-input{cursor:text}.gg-checkbox{accent-color:var(--text-color);width:1.2rem}.gg-range{accent-color:var(--text-color)}.gg-button{padding:0 .5rem}.gg-button.active{border:solid 1px var(--text-color-grad2);background-color:var(--text-color-grad2);color:var(--bkg-color)}.gg-button:disabled{color:gray;cursor:not-allowed;background-color:#d3d3d3;border:none}.gg-input:disabled,.gg-select:disabled,.gg-textarea:disabled{color:gray;cursor:not-allowed;background-color:#d3d3d3}.gg-button>img{vertical-align:middle}.gg-icon-button{color:var(--text-color);cursor:pointer;background-color:#0000;border:none;flex-direction:column;justify-content:center;align-items:center;padding:0;display:flex}.gg-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,.gg-select:hover,.gg-input:hover,.gg-textarea:hover,.gg-icon-button:hover{background-color:var(--bkg-color-grad1)}.gg-opacity{opacity:.5}.gg-opacity:hover{opacity:1;background-color:#0000}.gg-tabs{cursor:pointer;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}
53
55
  </style><style>
54
56
  #container,#ol-map{background-color:var(--bkg-color);width:100%;height:100%;position:relative}#ol-map.darkmap canvas{filter:invert()hue-rotate(180deg)}.hidden{display:none}.center{text-align:center;margin:10px;position:absolute;top:50%;left:50%;transform:translate(-50%,-50%)}.loading span{color:var(--text-color);margin-top:1rem;font-weight:600;display:block}.loading span.quote{font-style:italic;font-weight:300}#cs-map{background-color:var(--bkg-color);border-left:3px solid #444;height:100%;display:none;position:absolute;overflow:hidden}.ol-control{background-color:#0000;border:none;box-shadow:0 1px 4px #0000004d;right:2rem!important;left:unset!important}.ol-zoom{top:2rem!important}.mobile-map .ol-zoom{display:none!important}.ol-rotate{top:9.3rem!important}.ol-location{top:7rem!important}.img-location,.img-disable-location{width:55%;height:auto}.ol-zoom-in,.ol-zoom-out,.btn-location,.btn-disable-location,.ol-rotate-reset{cursor:pointer;background-color:var(--bkg-color)!important;width:2rem!important;height:2rem!important;color:var(--text-color)!important;font-size:1.2rem!important}.ol-scale-line{bottom:.5rem!important;right:1rem!important;left:unset!important}#swiper{width:100%;height:0;margin:0;display:none;position:absolute;top:50%}input[type=range]{-webkit-appearance:none;width:100%}input[type=range]::-webkit-slider-runnable-track{height:0}input[type=range]::-webkit-slider-thumb{-webkit-appearance:none;background:var(--bkg-color);cursor:ew-resize;border:2px solid #444;width:6px;height:0;margin-top:-1000px;padding-top:1000px;padding-bottom:1000px}input[type=range]::-moz-range-thumb{background:var(--bkg-color);cursor:ew-resize;border:2px solid #444;width:3px;height:0;padding-top:1000px;padding-bottom:1000px}.close-swiper{--button-size:2rem;transform:translateX(calc(-1 * var(--button-size) / 2));width:var(--button-size);height:var(--button-size);background-color:var(--bkg-color);color:var(--text-color);cursor:pointer;border-width:1px;border-radius:4px;display:none;position:absolute;top:0}.close-swiper:hover,.close-swiper:focus{outline:1px solid var(--text-color);color:var(--text-color-grad1)}.ol-viewport .tooltip{color:#fff;opacity:.7;white-space:nowrap;background:#00000080;border-radius:0;padding:.32rem .62rem;position:relative}@media screen and (hover:none){.ol-zoom,.ol-rotate,.ol-location{display:none!important}}.contextmenu{background-color:var(--bkg-color);color:var(--text-color);border:1px solid #ccc;box-shadow:1px 3px 4px #0006;& .hidden{display:none}& .menu-entry{cursor:pointer;padding:10px}& .menu-entry:hover{background-color:var(--bkg-color-grad1)}& .menu-entry[aria-disabled=true]{pointer-events:none;color:var(--text-color-grad1)}}
55
57
  </style>
56
58
  <style>${this.customStyle}</style>
57
- <link rel="stylesheet" href="lib/ol/ol.css"><div id="container"><div id="ol-map" class="${this.onMobile ? 'mobile-map' : ''}"></div><div class="${this.onMobile ? 'hidden' : 'ol-location ol-unselectable ol-control'}"><button onclick="${() => this.locateUser()}" class="btn-location"><img class="img-location" alt="location-icon" src="icons/adjust.svg"></button> <button id="disable-location" onclick="${() => this.disableLocateUser()}" class="btn-disable-location hidden"><img class="img-disable-location" alt="disable-location-icon" src="icons/adjust-disable.svg"></button></div><div id="cs-map"><div class="${this.loading ? 'loading center' : 'loading hidden'}"><img alt="loading-icon" src="icons/loading.svg" class="gg-spin"> <span i18n="Loading cesium...">Loading cesium...</span> <span class="quote" i18n="cesium-loading-quote">Please be patient, like a giraffe reaching for the tastiest leaves.</span></div></div><input id="swiper" type="range" min="0" max="1000" step="1"> <button tip="Hide Swiper" id="close-swiper" class="close-swiper">×</button></div>
59
+ <div id="container"><div id="ol-map" class="${this.onMobile ? 'mobile-map' : ''}"></div><div class="${this.onMobile ? 'hidden' : 'ol-location ol-unselectable ol-control'}"><button onclick="${() => this.locateUser()}" class="btn-location"><img class="img-location" alt="location-icon" src="icons/adjust.svg"></button> <button id="disable-location" onclick="${() => this.disableLocateUser()}" class="btn-disable-location hidden"><img class="img-disable-location" alt="disable-location-icon" src="icons/adjust-disable.svg"></button></div><div id="cs-map"><div class="${this.loading ? 'loading center' : 'loading hidden'}"><img alt="loading-icon" src="icons/loading.svg" class="gg-spin"> <span i18n="Loading cesium...">Loading cesium...</span> <span class="quote" i18n="cesium-loading-quote">Please be patient, like a giraffe reaching for the tastiest leaves.</span></div></div><input id="swiper" type="range" min="0" max="1000" step="1"> <button tip="Hide Swiper" id="close-swiper" class="close-swiper">×</button></div>
58
60
  ${this.htmlUnsafe(this.feedbackTemplateHtml ?? '')}`;
59
61
  };
60
62
  olMap;
@@ -57,9 +57,13 @@ declare class PrintComponent extends GirafeHTMLElement implements IGirafePanel {
57
57
  */
58
58
  onLayoutChanged(event: KeyboardEvent): void;
59
59
  /**
60
- * Set selected scale in the state and update the mask.
60
+ * Set the selected scale in the state and update the mask.
61
61
  */
62
62
  onScaleChanged(event: KeyboardEvent): void;
63
+ /**
64
+ * On pressing enter take the value from the event target (input)
65
+ * and set the selected scale in the state and update the mask.
66
+ */
63
67
  onCustomScaleTyped(evt: KeyboardEvent): void;
64
68
  /**
65
69
  * @Returns the current print scale.
@@ -113,10 +113,9 @@ ${this.htmlUnsafe(this.feedbackTemplateHtml ?? '')}`;
113
113
  this.render();
114
114
  }
115
115
  /**
116
- * Set selected scale in the state and update the mask.
116
+ * Set the selected scale in the state and update the mask.
117
117
  */
118
118
  onScaleChanged(event) {
119
- this.printMaskManager?.setManuallySelectedScale(false);
120
119
  if (this.context.configManager.Config.print?.customScale) {
121
120
  if (event.target?.value === 'custom') {
122
121
  this.showCustomScale = true;
@@ -128,19 +127,19 @@ ${this.htmlUnsafe(this.feedbackTemplateHtml ?? '')}`;
128
127
  }
129
128
  this.state.print.scale = parseInt(event.target?.value);
130
129
  this.printMaskManager?.zoomToScale(this.state.print.scale);
131
- this.printMaskManager?.setManuallySelectedScale(false);
132
130
  }
131
+ /**
132
+ * On pressing enter take the value from the event target (input)
133
+ * and set the selected scale in the state and update the mask.
134
+ */
133
135
  onCustomScaleTyped(evt) {
134
- // Only when the user presses the Enter key.
135
- if (evt.key === 'Enter') {
136
- const value = parseInt(evt.target?.value);
137
- if (value) {
138
- this.state.print.scale = value;
139
- this.printMaskManager?.zoomToScale(this.state.print.scale);
140
- this.render();
141
- this.printMaskManager?.setManuallySelectedScale(true);
142
- }
136
+ const value = parseInt(evt.target?.value);
137
+ if (evt.key !== 'Enter' || !value) {
138
+ return;
143
139
  }
140
+ this.state.print.scale = value;
141
+ this.printMaskManager?.zoomToScale(this.state.print.scale);
142
+ this.render();
144
143
  }
145
144
  /**
146
145
  * @Returns the current print scale.
@@ -10,12 +10,12 @@ declare class PrintMaskManager {
10
10
  private readonly printMaskLayer;
11
11
  private readonly map;
12
12
  private readonly eventsCallbacks;
13
+ private readonly olEventsKeys;
13
14
  private possibleScales;
14
- scaleManuallySelected: boolean;
15
+ private scaleManuallySelected;
15
16
  constructor(map: Map, stateManager: StateManager);
16
17
  private get state();
17
18
  destroy(): void;
18
- setManuallySelectedScale(value: boolean): void;
19
19
  /**
20
20
  * Sets the possible scales to fit the mask/view to.
21
21
  */
@@ -25,7 +25,9 @@ declare class PrintMaskManager {
25
25
  */
26
26
  zoomToScale(scale: number): void;
27
27
  /**
28
- * Register the events for printing pageSize and mask visibility changes.
28
+ * Register the events for:
29
+ * - printing pageSize and mask visibility changes.
30
+ * - set scaleManuallySelected to false when the resolution changes.
29
31
  */
30
32
  private registerEvents;
31
33
  /**
@@ -1,6 +1,7 @@
1
1
  import PrintMaskLayer from './printMaskLayer.js';
2
2
  import GeoConsts from '../../../tools/geoconsts.js';
3
3
  import { getOlayerByName } from '../../../tools/utils/olutils.js';
4
+ import { unByKey } from 'ol/Observable.js';
4
5
  const PRINT_MASK_LAYER_NAME = 'PrintMask';
5
6
  /**
6
7
  * Independent manager to display a mask layer adapted to the print.
@@ -11,6 +12,7 @@ class PrintMaskManager {
11
12
  printMaskLayer = new PrintMaskLayer({ name: PRINT_MASK_LAYER_NAME });
12
13
  map;
13
14
  eventsCallbacks = [];
15
+ olEventsKeys = [];
14
16
  possibleScales = [];
15
17
  scaleManuallySelected = false;
16
18
  constructor(map, stateManager) {
@@ -25,12 +27,11 @@ class PrintMaskManager {
25
27
  destroy() {
26
28
  this.stateManager.unsubscribe(this.eventsCallbacks);
27
29
  this.eventsCallbacks.length = 0;
30
+ unByKey(this.olEventsKeys);
31
+ this.olEventsKeys.length = 0;
28
32
  this.state.print.maskVisible = false;
29
33
  this.onPrintMaskVisibleChanged();
30
34
  }
31
- setManuallySelectedScale(value) {
32
- this.scaleManuallySelected = value;
33
- }
34
35
  /**
35
36
  * Sets the possible scales to fit the mask/view to.
36
37
  */
@@ -44,16 +45,27 @@ class PrintMaskManager {
44
45
  const mapSize = this.map.getSize() ?? [0, 0];
45
46
  const printMapSize = this.state.print.pageSize ?? [0, 0];
46
47
  const resolution = PrintMaskManager.getOptimalResolution(mapSize, printMapSize, scale);
47
- this.map.getView().setResolution(resolution);
48
+ const constraintRes = this.map.getView().getConstraints().resolution(resolution, 1, mapSize, undefined);
49
+ // Set the resolution (this emits the change:resolution event).
50
+ this.map.getView().setResolution(constraintRes);
51
+ // After the change:resolution event, force the map to render at the next key frame with
52
+ // scaleManuallySelected to true.
53
+ this.map.render();
54
+ this.scaleManuallySelected = true;
48
55
  }
49
56
  /**
50
- * Register the events for printing pageSize and mask visibility changes.
57
+ * Register the events for:
58
+ * - printing pageSize and mask visibility changes.
59
+ * - set scaleManuallySelected to false when the resolution changes.
51
60
  */
52
61
  registerEvents() {
53
62
  this.eventsCallbacks.push(...[
54
63
  this.stateManager.subscribe('print.pageSize', () => this.onPrintFormatChanged()),
55
64
  this.stateManager.subscribe('print.maskVisible', () => this.onPrintMaskVisibleChanged())
56
65
  ]);
66
+ this.olEventsKeys.push(this.map.getView().on('change:resolution', () => {
67
+ this.scaleManuallySelected = false;
68
+ }));
57
69
  }
58
70
  /**
59
71
  * Sets the print.scale value.
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.1.0-dev.2625220907",
8
+ "version": "1.1.0-dev.2629006744",
9
9
  "type": "module",
10
10
  "engines": {
11
11
  "node": ">=20.19.0"
package/styles/api.css CHANGED
@@ -1,2 +1,12 @@
1
+ /* SPDX-License-Identifier: Apache-2.0 */
1
2
  @import './variables.css';
2
3
  @import '../../node_modules/tippy.js/dist/tippy.css';
4
+
5
+ /** Define a default size for the map when integrated using the api.
6
+ * This will force the map to always be displayed, even if the user forgot to set the size.
7
+ */
8
+ geogirafe-map {
9
+ display: block;
10
+ width: 200px;
11
+ height: 200px;
12
+ }
@@ -9,6 +9,9 @@
9
9
 
10
10
  <meta http-equiv="X-UA-Compatible" content="ie=edge" />
11
11
 
12
+ <link rel="stylesheet" href="api.css" />
13
+ <script type="module" src="src/main.api.ts"></script>
14
+
12
15
  <title>GeoGirafe API</title>
13
16
  <style>
14
17
  body {
@@ -77,10 +80,8 @@
77
80
 
78
81
  geogirafe-map {
79
82
  position: absolute;
80
- display: block;
81
83
  width: 100%;
82
84
  height: 100%;
83
- resize: both;
84
85
  }
85
86
 
86
87
  ::-webkit-resizer {
@@ -119,9 +120,6 @@
119
120
  }
120
121
  </style>
121
122
 
122
- <script type="module" src="src/main.api.ts"></script>
123
- <link rel="stylesheet" href="api.css" />
124
-
125
123
  <script>
126
124
  document.addEventListener('DOMContentLoaded', () => {
127
125
  const apiOrigin = `${window.location.origin}${window.location.pathname.substring(0, window.location.pathname.lastIndexOf('/'))}`;
@@ -130,7 +128,7 @@
130
128
  elem.textContent = elem.textContent.replace('src="/geogirafe-api.js"', `src="${apiOrigin}/geogirafe-api.js"`);
131
129
  elem.textContent = elem.textContent.replace(
132
130
  'href="/geogirafe-api.css"',
133
- `src="${apiOrigin}/geogirafe-api.css"`
131
+ `href="${apiOrigin}/geogirafe-api.css"`
134
132
  );
135
133
 
136
134
  const sections = document.querySelectorAll('section');
@@ -1 +1 @@
1
- {"version":"1.1.0-dev.2625220907", "build":"2625220907", "date":"24/06/2026"}
1
+ {"version":"1.1.0-dev.2629006744", "build":"2629006744", "date":"25/06/2026"}
@@ -0,0 +1,8 @@
1
+ id point title description icon iconSize iconOffset
2
+ 1 2534337,1202168 Information Office de l'information<br />Tél: 032 000 00 00<br>Email: <a href="mailto:info@example.com">info@example.com</a><br />Internet: <a href="http://fr.wikipedia.org/wiki/La_Chaux-de-Fonds" target=new>Cliquer ici</a> api/marker-plus.png 21,25 -51,-90
3
+ 2 2534205,1202168 Ma première station Diesel pas cher api/marker2-plus.png 21,25 -51,-90
4
+ 3 2534605,1202168 Mon parking C'est celui-là le meilleur. api/marker2-plus.png 21,25 -51,-90
5
+ 4 2534542,1202168 Mon parking Ce parking est<br/>le meillleur. api/marker2-plus.png 21,25 -51,-90
6
+ 5 2534977,1202168 Ma deuxième station Sans-plomb pas cher. api/marker-plus.png 21,25 -51,-90
7
+ 6 2534631,1202175 Test marker 1. api/marker-plus.png 21,25 -51,-90
8
+ 7 2534472,1202507 Test marker 2. api/marker2-plus.png 21,25 -51,-90
@@ -124,7 +124,6 @@ export default defineConfig(({ command, mode }) => {
124
124
  { src: `${geogirafeSource}/styles/*.css`, dest: 'styles/' },
125
125
  { src: `${geogirafeSource}/assets/*`, dest: '' },
126
126
  { src: `${geogirafeSource}/tools/auth/silentlogincallback.html`, dest: '' },
127
- { src: 'node_modules/ol/ol.css', dest: 'lib/ol/' },
128
127
  { src: 'node_modules/tabulator-tables/dist/css/tabulator.min.css', dest: 'lib/tabulator-tables/' },
129
128
  { src: 'node_modules/tippy.js/dist/*.css', dest: 'lib/tippy.js/' },
130
129
  { src: 'node_modules/vanilla-picker/dist/*.css', dest: 'lib/vanilla-picker/' }
@@ -105,7 +105,7 @@ export default class GirafeContext {
105
105
  this.feedbackManager = new FeedbackManager(this);
106
106
  }
107
107
  async initialize() {
108
- // NOTE : This initialization order is important, because some singleton will need other ones !
108
+ // NOTE: This initialization order is important, because some singleton will need other ones!
109
109
  this.componentManager.initializeSingleton();
110
110
  this.userDataManager.initializeSingleton();
111
111
  this.configManager.initializeSingleton();
@@ -1,6 +1,7 @@
1
1
  import { SwipeupPanelMode } from './swipeuppanelmode.js';
2
2
  export default class GraphicalInterface {
3
3
  isMobile: boolean;
4
+ isApi: boolean;
4
5
  helpVisible: boolean;
5
6
  drawingPanelVisible: boolean;
6
7
  offlinePanelVisible: boolean;
@@ -1,6 +1,7 @@
1
1
  import { systemIsInDarkMode } from '../utils/utils.js';
2
2
  export default class GraphicalInterface {
3
3
  isMobile = false;
4
+ isApi = false;
4
5
  helpVisible = false;
5
6
  drawingPanelVisible = false;
6
7
  offlinePanelVisible = false;
@@ -105,10 +105,9 @@ export default class MockGirafeContext {
105
105
  this.themeFavoritesManager = new ThemeFavoritesManager(this);
106
106
  this.searchManager = new SearchManager(this);
107
107
  this.feedbackManager = new FeedbackManager(this);
108
- this.initialize();
109
108
  }
110
109
  async initialize() {
111
- // NOTE : This initialization order is important, because some singleton will need other ones !
110
+ // NOTE: This initialization order is important, because some singleton will need other ones!
112
111
  this.componentManager.initializeSingleton();
113
112
  this.userDataManager.initializeSingleton();
114
113
  this.userDataManager.setSource(MockConfig.userdata.source);
@@ -3,9 +3,11 @@ import proj4 from 'proj4';
3
3
  import { register } from 'ol/proj/proj4';
4
4
  import MockGirafeContext from './mockcontext';
5
5
  import { MockConfig } from './mockconfig';
6
+ import { noop } from '../utils/async';
6
7
  class MockHelper {
7
8
  static startMocking() {
8
9
  const context = new MockGirafeContext();
10
+ context.initialize().then(noop);
9
11
  for (const crs of MockConfig.crs) {
10
12
  proj4.defs(crs.code, crs.definition);
11
13
  }