@geogirafe/lib-geoportal 1.0.2217896135 → 1.0.2221886306

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.
@@ -1,6 +1,5 @@
1
+ import Feature from 'ol/Feature';
1
2
  import GirafeHTMLElement from '../../base/GirafeHTMLElement';
2
- import type { AllSearchResults } from '../../models/searchresult';
3
- import SearchResult from '../../models/searchresult';
4
3
  declare class SearchComponent extends GirafeHTMLElement {
5
4
  template: () => import("uhtml").Hole;
6
5
  searchIcon: string;
@@ -10,9 +9,10 @@ declare class SearchComponent extends GirafeHTMLElement {
10
9
  private previewLayers;
11
10
  private previewGeoLayer;
12
11
  private maxExtent?;
12
+ private readonly geoJsonFormatter;
13
13
  private ignoreBlur;
14
- groupedResults: Record<string, SearchResult[]>;
15
- protected allResults: SearchResult[];
14
+ groupedResults: Record<string, Feature[]>;
15
+ protected allResults: Feature[];
16
16
  protected forceHide: boolean;
17
17
  private readonly searchTermPlaceholder;
18
18
  private readonly searchLangPlaceholder;
@@ -39,7 +39,7 @@ declare class SearchComponent extends GirafeHTMLElement {
39
39
  protected connectedCallback(): void;
40
40
  protected clearSearch(purge?: boolean): void;
41
41
  doSearch(e: Event): Promise<void>;
42
- protected fetchSearch(term: string): Promise<AllSearchResults>;
42
+ protected fetchSearch(term: string): Promise<Feature[]>;
43
43
  /**
44
44
  * Debounce the fetch call to API to prevent sending request at every stroke.
45
45
  * @param e
@@ -52,14 +52,14 @@ declare class SearchComponent extends GirafeHTMLElement {
52
52
  private displayCoordinates;
53
53
  private displayResults;
54
54
  getIcon(searchGroup: string): any;
55
- onMouseOver(result: SearchResult): void;
55
+ onMouseOver(result: Feature): void;
56
56
  onMouseLeave(): void;
57
57
  private focusResultFromIndex;
58
58
  private focusResult;
59
59
  private preview;
60
60
  private addFeatureToPreview;
61
61
  clearPreview(): void;
62
- onSelect(result: SearchResult): void;
62
+ onSelect(feature: Feature): void;
63
63
  private addResultToTreeView;
64
64
  private zoomTo;
65
65
  onMouseMove(): void;
@@ -1,9 +1,10 @@
1
1
  import { html as uHtml } from 'uhtml';
2
2
  import Collection from 'ol/Collection';
3
3
  import Feature from 'ol/Feature';
4
+ import GeoJSON from 'ol/format/GeoJSON';
4
5
  import VectorSource from 'ol/source/Vector';
5
6
  import VectorLayer from 'ol/layer/Vector';
6
- import { LineString, MultiLineString, MultiPolygon, Point, MultiPoint, Polygon } from 'ol/geom';
7
+ import { Point } from 'ol/geom';
7
8
  import { Style, Icon, Stroke, Fill } from 'ol/style';
8
9
  import { buffer, getWidth, getHeight, getCenter, containsExtent } from 'ol/extent';
9
10
  import GirafeColorPicker from '../../tools/utils/girafecolorpicker';
@@ -21,7 +22,7 @@ class SearchComponent extends GirafeHTMLElement {
21
22
  </style><style>
22
23
  .hidden{display:none}#searchbox{background-color:var(--bkg-color);border:1px solid #cfd6dd;border-radius:4px;flex-direction:row;width:100%;height:2.25rem;display:flex}#search{-webkit-appearance:none;width:auto;height:2.25rem;color:var(--text-color);background:0 0;border:0;outline:none;-webkit-flex:auto;-ms-flex:auto;flex:auto;margin:0 0 0 .5rem;padding-left:.8rem;font-size:1rem}.search-icon,.close-icon,.select-color-icon{width:14px;color:var(--text-color);padding:0 1rem}.close-button,.select-color-button{cursor:pointer;background-color:#0000;border:none;padding:0}#results{z-index:1000;background-color:var(--bkg-color);scrollbar-width:thin;width:36rem;max-height:31rem;margin-top:.1rem;margin-bottom:.5rem;position:absolute;top:3.5rem;overflow-x:hidden}.result,.title{width:34rem;padding:.3rem 1rem;line-height:1.3rem;display:inline-block}.title{color:var(--text-color);padding-top:1.5rem;font-size:1.3rem;font-weight:600;line-height:2.5rem}.result{cursor:pointer;text-align:left;width:100%;color:var(--text-color);background:0 0;border:none;border-top:1px solid #ccc}.title img{width:14px;margin-right:1rem}.title span{text-transform:uppercase}.result.selected{color:var(--text-color);background-color:#aaa!important}#no-result{background:var(--warning-color);opacity:.75;visibility:hidden;pointer-events:none;border-radius:6px;width:fit-content;margin:auto;padding:.5rem 0;font-size:1rem;position:absolute;top:3.5rem;left:0;right:0}#no-result>span{padding:1.3rem}@keyframes fadeOut{0%{opacity:1}80%{opacity:1}to{opacity:0}}.shown-then-fadeout{animation:2s ease-in-out forwards fadeOut;visibility:visible!important}
23
24
  </style>
24
- <link rel="stylesheet" href="lib/vanilla-picker/vanilla-picker.csp.css"><link rel="stylesheet" href="styles/girafecolorpicker.css"><div id="searchbox"><input tip="search_tip" id="search" class="gg-input" length="20" maxlength="1000" autocomplete="off" autocorrect="off" i18n="Search..." placeholder="Search..." oninput="${(e) => this.doSearchDebounced(e)}" onfocusin="${() => this.onFocusIn()}" onfocusout="${() => this.onFocusOut()}" onkeydown="${(e) => this.onKeyDown(e)}"> <img class="${this.allResults.length > 0 ? 'hidden' : 'search-icon'}" alt="search-icon" src="${this.searchIcon}"> <button class="${this.allResults.length > 0 ? 'close-button' : 'hidden'}" onclick="${() => this.clearSearch(true)}"><img class="close-icon" alt="close-icon" src="icons/close.svg"></button> <button tip="change_search_result_color" id="colorPickerBtn" class="${this.paintSearchResults ? 'select-color-button' : 'hidden'}"><img class="select-color-icon" alt="select-color-icon" src="${this.paintbrushIcon}"></button><div id="results" class="${Object.keys(this.groupedResults).length === 0 || this.forceHide ? 'hidden' : ''}" onmousemove="${() => this.onMouseMove()}">${Object.keys(this.groupedResults).map(group => uHtml `<div class="title"><img alt="result icon" src="${this.getIcon(group)}"> <span i18n="${group}"></span></div>${this.groupedResults[group].map(result => uHtml ` <button class="${result.selected ? 'result selected' : 'result'}" onmousedown="${() => this.onMouseDown()}" onclick="${() => this.onSelect(result)}" onmouseover="${() => this.onMouseOver(result)}" onmouseleave="${() => this.onMouseLeave()}">${result.properties?.label}</button> `)} `)}</div><div id="no-result" class="${this.showNoResultWarning ? 'shown-then-fadeout' : ''}"><span i18n="${'No match found'}"></span></div></div>`;
25
+ <link rel="stylesheet" href="lib/vanilla-picker/vanilla-picker.csp.css"><link rel="stylesheet" href="styles/girafecolorpicker.css"><div id="searchbox"><input tip="search_tip" id="search" class="gg-input" length="20" maxlength="1000" autocomplete="off" autocorrect="off" i18n="Search..." placeholder="Search..." oninput="${(e) => this.doSearchDebounced(e)}" onfocusin="${() => this.onFocusIn()}" onfocusout="${() => this.onFocusOut()}" onkeydown="${(e) => this.onKeyDown(e)}"> <img class="${this.allResults.length > 0 ? 'hidden' : 'search-icon'}" alt="search-icon" src="${this.searchIcon}"> <button class="${this.allResults.length > 0 ? 'close-button' : 'hidden'}" onclick="${() => this.clearSearch(true)}"><img class="close-icon" alt="close-icon" src="icons/close.svg"></button> <button tip="change_search_result_color" id="colorPickerBtn" class="${this.paintSearchResults ? 'select-color-button' : 'hidden'}"><img class="select-color-icon" alt="select-color-icon" src="${this.paintbrushIcon}"></button><div id="results" class="${Object.keys(this.groupedResults).length === 0 || this.forceHide ? 'hidden' : ''}" onmousemove="${() => this.onMouseMove()}">${Object.keys(this.groupedResults).map(group => uHtml `<div class="title"><img alt="result icon" src="${this.getIcon(group)}"> <span i18n="${group}"></span></div>${this.groupedResults[group].map(result => uHtml ` <button class="${result.get('selected') ? 'result selected' : 'result'}" onmousedown="${() => this.onMouseDown()}" onclick="${() => this.onSelect(result)}" onmouseover="${() => this.onMouseOver(result)}" onmouseleave="${() => this.onMouseLeave()}">${result.get('label')}</button> `)} `)}</div><div id="no-result" class="${this.showNoResultWarning ? 'shown-then-fadeout' : ''}"><span i18n="${'No match found'}"></span></div></div>`;
25
26
  };
26
27
  searchIcon = SearchIcon;
27
28
  paintbrushIcon = PaintbrushIcon;
@@ -32,6 +33,7 @@ class SearchComponent extends GirafeHTMLElement {
32
33
  previewLayers = [];
33
34
  previewGeoLayer = null;
34
35
  maxExtent;
36
+ geoJsonFormatter = new GeoJSON();
35
37
  ignoreBlur = false;
36
38
  groupedResults = {};
37
39
  allResults = [];
@@ -56,9 +58,9 @@ class SearchComponent extends GirafeHTMLElement {
56
58
  async initialSearch() {
57
59
  const searchTerm = this.context.permalinkManager.getSearchTerm();
58
60
  const results = await this.fetchSearch(searchTerm);
59
- if (results.features.length > 0) {
61
+ if (results.length > 0) {
60
62
  // Apply the first search result in the list
61
- const firstResult = results.features[0];
63
+ const firstResult = results[0];
62
64
  this.preview(firstResult);
63
65
  this.onSelect(firstResult);
64
66
  }
@@ -149,13 +151,13 @@ class SearchComponent extends GirafeHTMLElement {
149
151
  }
150
152
  if (term.length > 0) {
151
153
  try {
152
- const data = await this.fetchSearch(term);
154
+ const features = await this.fetchSearch(term);
153
155
  // If the search term is at least two charecter but yieds no result, a warning
154
156
  // box is displayed for 2 seconds and then fades out (CSS)
155
- if (data.features.length === 0 && term.length >= 2) {
157
+ if (features.length === 0 && term.length >= 2) {
156
158
  this.showNoResultWarning = true;
157
159
  }
158
- this.displayResults(data);
160
+ this.displayResults(features);
159
161
  }
160
162
  catch (error) {
161
163
  if (error instanceof DOMException && error.name === 'AbortError') {
@@ -172,7 +174,11 @@ class SearchComponent extends GirafeHTMLElement {
172
174
  .replace(this.searchLangPlaceholder, this.state.language);
173
175
  const response = await fetch(url, { signal: this.abortController.signal });
174
176
  const data = await response.json();
175
- return data;
177
+ const features = this.geoJsonFormatter.readFeatures(data, {
178
+ dataProjection: this.context.configManager.getDefaultConfigValue('search.resultsSrid'),
179
+ featureProjection: this.map.getView().getProjection()
180
+ });
181
+ return features;
176
182
  }
177
183
  /**
178
184
  * Debounce the fetch call to API to prevent sending request at every stroke.
@@ -206,40 +212,32 @@ class SearchComponent extends GirafeHTMLElement {
206
212
  if (!east_coord || !north_coord) {
207
213
  return;
208
214
  }
209
- const result = {
210
- bbox: [east_coord, north_coord, east_coord, north_coord],
211
- geometry: {
212
- type: 'Point',
213
- coordinates: [east_coord, north_coord]
214
- },
215
- properties: {
216
- label: `${coord1} ${coord2}`,
217
- layer_name: 'recenter_map'
218
- }
219
- };
220
- this.allResults = [result];
221
- this.groupedResults.recenter_map = [result];
215
+ const feature = new Feature({
216
+ geometry: new Point([east_coord, north_coord]),
217
+ label: `${coord1} ${coord2}`,
218
+ layer_name: 'recenter_map'
219
+ });
220
+ this.allResults = [feature];
221
+ this.groupedResults.recenter_map = [feature];
222
222
  super.render();
223
223
  super.girafeTranslate();
224
224
  }
225
- displayResults(results) {
225
+ displayResults(features) {
226
226
  // First, group the results
227
- for (const result of results.features) {
227
+ for (const result of features) {
228
228
  // results.features.forEach((result) => {
229
229
  let type = 'Unknown layer type';
230
- if (result.properties) {
231
- if (result.properties.layer_name) {
232
- type = result.properties.layer_name;
233
- }
234
- else if (result.properties.actions[0].action.startsWith('add_theme')) {
235
- type = 'add_theme';
236
- }
237
- else if (result.properties.actions[0].action.startsWith('add_group')) {
238
- type = 'add_group';
239
- }
240
- else if (result.properties.actions[0].action.startsWith('add_layer')) {
241
- type = 'add_layer';
242
- }
230
+ if (result.get('layer_name')) {
231
+ type = result.get('layer_name');
232
+ }
233
+ else if (result.get('actions')[0].action.startsWith('add_theme')) {
234
+ type = 'add_theme';
235
+ }
236
+ else if (result.get('actions')[0].action.startsWith('add_group')) {
237
+ type = 'add_group';
238
+ }
239
+ else if (result.get('actions')[0].action.startsWith('add_layer')) {
240
+ type = 'add_layer';
243
241
  }
244
242
  let resultList;
245
243
  if (type in this.groupedResults) {
@@ -284,12 +282,12 @@ class SearchComponent extends GirafeHTMLElement {
284
282
  // Clear old selection and preview
285
283
  this.clearPreview();
286
284
  if (this.focusedResult) {
287
- this.focusedResult.selected = false;
285
+ this.focusedResult.set('selected', false);
288
286
  }
289
287
  // Set new selected object, and activate preview
290
288
  this.focusedResultIndex = this.allResults.findIndex((r) => r === result);
291
289
  this.focusedResult = this.allResults[this.focusedResultIndex];
292
- this.focusedResult.selected = true;
290
+ this.focusedResult.set('selected', true);
293
291
  this.render();
294
292
  this.preview(result);
295
293
  // Scroll to selected div
@@ -297,14 +295,12 @@ class SearchComponent extends GirafeHTMLElement {
297
295
  resultHtmlElement.scrollIntoView({ block: 'nearest' });
298
296
  }
299
297
  preview(result) {
300
- if (result.bbox && this.context.configManager.Config.search.objectPreview) {
298
+ if (result.getGeometry() && this.context.configManager.Config.search.objectPreview) {
301
299
  // Result with geometry
302
- if (result.geometry) {
303
- this.addFeatureToPreview(result.geometry);
304
- this.updatePreviewLayerStyle();
305
- }
300
+ this.addFeatureToPreview(result);
301
+ this.updatePreviewLayerStyle();
306
302
  }
307
- const firstAction = result.properties?.actions?.[0];
303
+ const firstAction = result.get('actions')?.[0];
308
304
  if (firstAction?.action.startsWith('add_layer') && this.context.configManager.Config.search.layerPreview) {
309
305
  const layer = this.context.themesHelper.findLayerByName(firstAction.data);
310
306
  if (layer) {
@@ -317,47 +313,8 @@ class SearchComponent extends GirafeHTMLElement {
317
313
  }
318
314
  }
319
315
  }
320
- addFeatureToPreview(geometry) {
321
- switch (geometry.type) {
322
- case 'Point': {
323
- const feature = new Feature(new Point(geometry.coordinates));
324
- this.previewFeaturesCollection.push(feature);
325
- return;
326
- }
327
- case 'MultiPoint': {
328
- const feature = new Feature(new MultiPoint(geometry.coordinates));
329
- this.previewFeaturesCollection.push(feature);
330
- return;
331
- }
332
- case 'MultiLineString': {
333
- const feature = new Feature(new MultiLineString(geometry.coordinates));
334
- this.previewFeaturesCollection.push(feature);
335
- return;
336
- }
337
- case 'LineString': {
338
- const feature = new Feature(new LineString(geometry.coordinates));
339
- this.previewFeaturesCollection.push(feature);
340
- return;
341
- }
342
- case 'Polygon': {
343
- const feature = new Feature(new Polygon(geometry.coordinates));
344
- this.previewFeaturesCollection.push(feature);
345
- return;
346
- }
347
- case 'MultiPolygon': {
348
- const feature = new Feature(new MultiPolygon(geometry.coordinates));
349
- this.previewFeaturesCollection.push(feature);
350
- return;
351
- }
352
- case 'GeometryCollection': {
353
- for (const geom of geometry.geometries) {
354
- this.addFeatureToPreview(geom);
355
- }
356
- return;
357
- }
358
- default:
359
- throw new Error('Geometry type of search result is not being supported.');
360
- }
316
+ addFeatureToPreview(feature) {
317
+ this.previewFeaturesCollection.push(feature);
361
318
  }
362
319
  clearPreview() {
363
320
  // Clear preview search result
@@ -366,46 +323,48 @@ class SearchComponent extends GirafeHTMLElement {
366
323
  this.context.themesHelper.removeLayersFromLayerTree(this.previewLayers);
367
324
  this.previewLayers = [];
368
325
  }
369
- onSelect(result) {
370
- this.selectedResult = result;
326
+ onSelect(feature) {
327
+ this.selectedResult = feature;
371
328
  this.ignoreBlur = false;
372
329
  this.forceHide = true;
373
330
  this.previewLayers = [];
374
331
  super.render();
375
- if (result.bbox) {
332
+ const geom = feature.getGeometry();
333
+ if (geom) {
376
334
  // Result with geometry
377
- this.zoomTo(result.bbox);
335
+ this.zoomTo(geom.getExtent());
378
336
  }
379
337
  else {
380
- this.addResultToTreeView(result);
338
+ this.addResultToTreeView(feature);
381
339
  }
382
340
  this.onFocusOut();
383
341
  // Update searchbox with result
384
- if (this.searchInput && result.properties) {
385
- this.searchInput.value = result.properties.label;
342
+ if (this.searchInput && feature.get('label')) {
343
+ this.searchInput.value = feature.get('label');
386
344
  }
387
345
  }
388
- addResultToTreeView(result) {
346
+ addResultToTreeView(feature) {
389
347
  let clonedTheme;
390
- if (!result.properties || result.properties.actions.length === 0) {
348
+ const actions = feature.get('actions');
349
+ if (!actions || actions.length === 0) {
391
350
  // Nothing to add
392
351
  return;
393
352
  }
394
353
  let activate = false;
395
- if (result.properties.actions[0].action.startsWith('add_theme')) {
396
- const theme = this.context.themesHelper.findThemeByName(result.properties.actions[0].data);
354
+ if (actions[0].action.startsWith('add_theme')) {
355
+ const theme = this.context.themesHelper.findThemeByName(actions[0].data);
397
356
  if (theme) {
398
357
  clonedTheme = theme.clone();
399
358
  }
400
359
  }
401
- else if (result.properties.actions[0].action.startsWith('add_group')) {
402
- const group = this.context.themesHelper.findGroupByName(result.properties.actions[0].data);
360
+ else if (actions[0].action.startsWith('add_group')) {
361
+ const group = this.context.themesHelper.findGroupByName(actions[0].data);
403
362
  if (group) {
404
363
  clonedTheme = this.context.themesHelper.getMinimalClonedThemeForLayer(group);
405
364
  }
406
365
  }
407
- else if (result.properties.actions[0].action.startsWith('add_layer')) {
408
- const layer = this.context.themesHelper.findLayerByName(result.properties.actions[0].data);
366
+ else if (actions[0].action.startsWith('add_layer')) {
367
+ const layer = this.context.themesHelper.findLayerByName(actions[0].data);
409
368
  if (layer) {
410
369
  clonedTheme = this.context.themesHelper.getMinimalClonedThemeForLayer(layer);
411
370
  activate = true;
@@ -7,7 +7,7 @@ class MobileSearchComponent extends SearchComponent {
7
7
  </style><style>
8
8
  .hidden{display:none}#searchbox{background-color:var(--bkg-color);z-index:101;border:none;border-radius:4px;flex-direction:row;width:100%;display:flex;position:absolute;box-shadow:0 0 10px 1px #0003}#search{z-index:2;-webkit-appearance:none;width:100%;color:var(--text-color);background:0 0;border:0;outline:none;-webkit-flex:auto;-ms-flex:auto;flex:auto;margin:0 0 0 .5rem;padding-left:.8rem;font-size:1.8em;line-height:1.8em}#search::placeholder{color:inherit;opacity:.4}#search:focus::placeholder{opacity:0;color:#0000;transition:opacity .3s}.search-icon,.close-icon,.select-color-icon{width:17px;color:var(--text-color);opacity:.7;padding:0 1rem}.close-button,.select-color-button{cursor:pointer;background-color:#0000;border:none;padding:0}#results{z-index:100;background-color:var(--bkg-color);scrollbar-width:thin;padding-top:7vh;position:fixed;inset:0;overflow-x:hidden}.result,.title{width:34rem;padding:.3rem 1rem;line-height:1.3rem;display:inline-block}.title{color:var(--text-color);padding-top:1.5rem;font-size:1.3rem;font-weight:600;line-height:2.5rem}.result{cursor:pointer;text-align:left;width:100%;color:var(--text-color);background:0 0;border:none;border-top:1px solid #ccc}.title img{width:14px;margin-right:1rem}.title span{text-transform:uppercase}.result.selected{color:var(--text-color);background-color:#aaa!important}#no-result{background:var(--warning-color);opacity:.75;visibility:hidden;pointer-events:none;border-radius:6px;width:fit-content;margin:auto;padding:.5rem 0;font-size:1rem;position:absolute;top:3rem;left:0;right:0}#no-result>span{padding:1.3rem}@keyframes fadeOut{0%{opacity:1}80%{opacity:1}to{opacity:0}}.shown-then-fadeout{animation:2s ease-in-out forwards fadeOut;visibility:visible!important}
9
9
  </style>
10
- <link rel="stylesheet" href="lib/vanilla-picker/vanilla-picker.csp.css"><div id="searchbox"><input id="search" class="gg-input" length="20" maxlength="1000" autocomplete="off" autocorrect="off" i18n="Search" placeholder="Search..." oninput="${(e) => this.doSearchDebounced(e)}" onkeydown="${(e) => this.onKeyDown(e)}"> <img class="${this.allResults.length > 0 ? 'hidden' : 'search-icon'}" alt="search-icon" src="${this.searchIcon}"> <button class="${this.allResults.length > 0 ? 'close-button' : 'hidden'}" onclick="${() => this.clearSearch(true)}"><img class="close-icon" alt="close-icon" src="icons/close.svg"></button></div><div id="results" class="${Object.keys(this.groupedResults).length === 0 || this.forceHide ? 'hidden' : ''}">${Object.keys(this.groupedResults).map(group => uHtml `<div class="title"><img alt="result icon" src="${this.getIcon(group)}"> <span i18n="${group}"></span></div>${this.groupedResults[group].map(result => uHtml ` <button class="${result.selected ? 'result selected' : 'result'}" onmousedown="${() => this.onMouseDown()}" onclick="${() => this.onSelect(result)}">${result.properties?.label}</button> `)} `)}</div><div id="no-result" class="${this.showNoResultWarning ? 'shown-then-fadeout' : ''}"><span i18n="${'No match found'}"></span></div>`;
10
+ <link rel="stylesheet" href="lib/vanilla-picker/vanilla-picker.csp.css"><div id="searchbox"><input id="search" class="gg-input" length="20" maxlength="1000" autocomplete="off" autocorrect="off" i18n="Search" placeholder="Search..." oninput="${(e) => this.doSearchDebounced(e)}" onkeydown="${(e) => this.onKeyDown(e)}"> <img class="${this.allResults.length > 0 ? 'hidden' : 'search-icon'}" alt="search-icon" src="${this.searchIcon}"> <button class="${this.allResults.length > 0 ? 'close-button' : 'hidden'}" onclick="${() => this.clearSearch(true)}"><img class="close-icon" alt="close-icon" src="icons/close.svg"></button></div><div id="results" class="${Object.keys(this.groupedResults).length === 0 || this.forceHide ? 'hidden' : ''}">${Object.keys(this.groupedResults).map(group => uHtml `<div class="title"><img alt="result icon" src="${this.getIcon(group)}"> <span i18n="${group}"></span></div>${this.groupedResults[group].map(result => uHtml ` <button class="${result.get('selected') ? 'result selected' : 'result'}" onmousedown="${() => this.onMouseDown()}" onclick="${() => this.onSelect(result)}">${result.get('label')}</button> `)} `)}</div><div id="no-result" class="${this.showNoResultWarning ? 'shown-then-fadeout' : ''}"><span i18n="${'No match found'}"></span></div>`;
11
11
  };
12
12
  }
13
13
  export default MobileSearchComponent;
package/models/main.d.ts CHANGED
@@ -31,8 +31,7 @@ export { default as ThemeLayer } from './layers/themelayer';
31
31
  export { default as ThemeLayerExternal } from './layers/themelayerexternal';
32
32
  export type { TimeAwareLayer } from './layers/timeawarelayer';
33
33
  export { isTimeAwareLayer } from './layers/timeawarelayer';
34
- export type { GeometryResult, GeometryCollectionResult, AllSearchResults } from './searchresult';
35
- export { default as SearchResult } from './searchresult';
34
+ export type { SearchResultsActions } from './searchresult';
36
35
  export type { SelectionTool, SelectionMode } from './selection';
37
36
  export { default as SelectionParam } from './selectionparam';
38
37
  export { default as ServerOgc } from './serverogc';
package/models/main.js CHANGED
@@ -21,7 +21,6 @@ export { isSnappableLayer } from './layers/snappablelayer';
21
21
  export { default as ThemeLayer } from './layers/themelayer';
22
22
  export { default as ThemeLayerExternal } from './layers/themelayerexternal';
23
23
  export { isTimeAwareLayer } from './layers/timeawarelayer';
24
- export { default as SearchResult } from './searchresult';
25
24
  export { default as SelectionParam } from './selectionparam';
26
25
  export { default as ServerOgc } from './serverogc';
27
26
  export { default as ServerOgcApi, OgcApiDefaultEncoding } from './serverogcapi';
@@ -1,27 +1,4 @@
1
- import { Coordinate } from 'ol/coordinate';
2
- import { Extent } from 'ol/extent';
3
- export type GeometryResult = {
4
- type: 'Point' | 'MultiPoint' | 'LineString' | 'MultiLineString' | 'Polygon' | 'MultiPolygon';
5
- coordinates: Coordinate | Coordinate[] | Coordinate[][] | Coordinate[][][];
1
+ export type SearchResultsActions = {
2
+ action: string;
3
+ data: string;
6
4
  };
7
- export type GeometryCollectionResult = {
8
- type: 'GeometryCollection';
9
- geometries: GeometryCollectionResult[] | GeometryResult[];
10
- };
11
- export type AllSearchResults = {
12
- type: string;
13
- features: SearchResult[];
14
- };
15
- export default class SearchResult {
16
- bbox?: Extent;
17
- geometry?: GeometryResult | GeometryCollectionResult;
18
- properties?: {
19
- label: string;
20
- layer_name: string;
21
- actions: {
22
- action: string;
23
- data: string;
24
- }[];
25
- };
26
- selected: boolean;
27
- }
@@ -1,6 +1 @@
1
- export default class SearchResult {
2
- bbox;
3
- geometry;
4
- properties;
5
- selected = false;
6
- }
1
+ export {};
package/package.json CHANGED
@@ -5,7 +5,7 @@
5
5
  "name": "GeoGirafe PSC",
6
6
  "url": "https://doc.geomapfish.dev"
7
7
  },
8
- "version": "1.0.2217896135",
8
+ "version": "1.0.2221886306",
9
9
  "type": "module",
10
10
  "engines": {
11
11
  "node": ">=20.19.0"
@@ -1 +1 @@
1
- {"version":"1.0.2217896135", "build":"2217896135", "date":"16/12/2025"}
1
+ {"version":"1.0.2221886306", "build":"2221886306", "date":"18/12/2025"}
@@ -20,6 +20,7 @@ export default class AbstractConnectManager {
20
20
  this.state.oauth.status = 'loginFailed';
21
21
  }
22
22
  loggedOut() {
23
+ this.context.sessionManager.saveStateToSession();
23
24
  this.state.oauth.status = 'loggedOut';
24
25
  this.state.oauth.tokens = undefined;
25
26
  this.state.oauth.userInfo = undefined;
@@ -81,8 +81,12 @@ export default class AuthManager extends GirafeSingleton {
81
81
  await this.gmfManager.loginWithToken();
82
82
  }
83
83
  // And then we get the UserInfos
84
+ const previousUsername = this.state.oauth.userInfo?.username;
84
85
  await this.gmfManager.getUserInfo();
85
86
  if (this.state.oauth.userInfo?.username) {
87
+ // TODO REG : At the moment we just test if the username has changed,
88
+ // but actually we should test if some of the rights have changed
89
+ this.state.oauth.somethingChanged = this.state.oauth.userInfo.username !== previousUsername;
86
90
  this.state.oauth.status = 'loggedIn';
87
91
  }
88
92
  else {
@@ -33,7 +33,6 @@ export default class GMFManager {
33
33
  throw new Error(gmfLoginResponse.statusText);
34
34
  }
35
35
  }
36
- this.state.oauth.status = 'loggedIn';
37
36
  }
38
37
  async logout() {
39
38
  console.debug('Auth: 4. Backend logout');
@@ -178,7 +178,9 @@ export default class OpenIdConnectManager extends AbstractConnectManager {
178
178
  this.state.oauth.tokens = tokens;
179
179
  // Prepare refresh token
180
180
  if (this.state.oauth.tokens.expires_in) {
181
- const expiresInMs = this.state.oauth.tokens.expires_in * 1000;
181
+ // Refresh the token 1 minutes before its expiration,
182
+ // to be sure that it will still be valid when using it.
183
+ const expiresInMs = (this.state.oauth.tokens.expires_in - 60) * 1000;
182
184
  setTimeout(() => this.refreshToken(), expiresInMs);
183
185
  }
184
186
  this.state.oauth.status = 'issuer.loggedIn';
@@ -38,6 +38,7 @@ declare class GirafeConfig {
38
38
  };
39
39
  search: {
40
40
  url: string;
41
+ resultsSrid: string;
41
42
  objectPreview?: boolean;
42
43
  layerPreview?: boolean;
43
44
  minResolution?: number;
@@ -73,7 +73,8 @@ class GirafeConfig {
73
73
  // We just display a warning in the console
74
74
  console.warn(e);
75
75
  this.search = {
76
- url: ''
76
+ url: '',
77
+ resultsSrid: config.map.srid
77
78
  };
78
79
  }
79
80
  try {
@@ -192,6 +193,7 @@ class GirafeConfig {
192
193
  }
193
194
  return {
194
195
  url: config.search.url,
196
+ resultsSrid: config.search.resultsSrid ?? config.map.srid,
195
197
  objectPreview: config.search.objectPreview ?? false,
196
198
  layerPreview: config.search.layerPreview ?? false,
197
199
  minResolution: config.search.minResolution ?? 0.5,
@@ -65,6 +65,7 @@ type LoginState = {
65
65
  userInfo?: UserInfo;
66
66
  audience: string[];
67
67
  error?: string;
68
+ somethingChanged: boolean;
68
69
  };
69
70
  export type InfoWindow = {
70
71
  title: string | null;
@@ -99,7 +99,8 @@ export default class State {
99
99
  isOffline = false;
100
100
  oauth = {
101
101
  status: 'not-initialized',
102
- audience: []
102
+ audience: [],
103
+ somethingChanged: true
103
104
  };
104
105
  // The State object is defined as <not extensible> by the StateManager.
105
106
  // This property can be used by third-parts components or extensions
@@ -30,7 +30,8 @@ class ThemesManager extends GirafeSingleton {
30
30
  // This is for the next change in the oAuth status.
31
31
  this.context.stateManager.subscribe('oauth.status', async () => {
32
32
  if (this.state.themes.isLoaded &&
33
- (this.state.oauth.status === 'loggedIn' || this.state.oauth.status === 'loggedOut')) {
33
+ ((this.state.oauth.somethingChanged && this.state.oauth.status === 'loggedIn') ||
34
+ this.state.oauth.status === 'loggedOut')) {
34
35
  await this.initialize();
35
36
  }
36
37
  });