@geogirafe/lib-geoportal 1.2.0-dev.2666688189 → 1.2.0-dev.2675426009

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.
@@ -123,8 +123,10 @@ ${this.htmlUnsafe(this.feedbackTemplateHtml ?? '')}`;
123
123
  }
124
124
  manageUserInteraction() {
125
125
  // Deactivate the preview of search results
126
- this.context.configManager.Config.search.objectPreview = false;
127
- this.context.configManager.Config.search.layerPreview = false;
126
+ if (this.context.configManager.Config.search) {
127
+ this.context.configManager.Config.search.objectPreview = false;
128
+ this.context.configManager.Config.search.layerPreview = false;
129
+ }
128
130
  // Force window as selection component
129
131
  this.state.interface.selectionComponent = 'window';
130
132
  // Deactivate selection if the selectionbox is not active
@@ -284,7 +284,7 @@
284
284
  "Layers (from Capabilities)": "Layer (aus Capabilities)",
285
285
  "layers selected": "ausgewählte Ebene(n)",
286
286
  "Layout": "Layout",
287
- "layout-panel": "Schnittstellenlayout",
287
+ "layout-panel": "Ansichtslayout",
288
288
  "less than": "kleiner als",
289
289
  "less than or equal to": "kleiner oder gleich",
290
290
  "LiDAR profile service error": "Fehler beim LiDAR-Profil-Dienst",
@@ -3,14 +3,18 @@ export default class OauthComponent extends GirafeHTMLElement {
3
3
  protected templateUrl: string | null;
4
4
  protected styleUrls: string[] | null;
5
5
  template: () => import("uhtml").Hole;
6
- userIconUrl?: string;
7
6
  constructor(name?: string);
8
7
  private registerEvents;
9
8
  getDisplayName(): string;
10
- refreshRender(): void;
9
+ getFullName(): string | null;
10
+ /**
11
+ * Gets the user initials
12
+ * super_admin -> SA
13
+ * Gustave-André de la Girouette -> GG
14
+ * @returns
15
+ */
16
+ getInitials(): string;
11
17
  onLoginClick(): void;
12
18
  onLogoutClick(): Promise<void>;
13
- getUserIconUrl(): Promise<string | undefined>;
14
- private sha256;
15
19
  protected connectedCallback(): void;
16
20
  }
@@ -8,13 +8,12 @@ export default class OauthComponent extends GirafeHTMLElement {
8
8
  return uHtml `<style>
9
9
  .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}}
10
10
  </style><style>
11
- .container{border-right:var(--app-standard-border);flex-direction:row;align-items:center;display:flex}.name{text-align:left;flex-direction:column;display:flex}.user-avatar{--svg-filter:none}
11
+ .container{border-right:var(--app-standard-border);flex-direction:row;align-items:center;display:flex}.name{text-align:left;flex-direction:column;display:flex}.avatar-initials{background-color:var(--app-color-grey);color:#000;border-radius:50%;justify-content:center;align-items:center;width:2rem;height:2rem;font-weight:700;display:flex}.gg-icon-button.logged-in{flex-direction:row;gap:.5rem;padding:.5rem}
12
12
  </style>
13
13
  <style>${this.customStyle}</style>
14
- <div class="${this.context.configManager.Config.oauth || this.context.configManager.Config.gmfauth ? '' : 'hidden'}"><div class="${this.state.oauth.status === 'loggedIn' ? 'hidden' : 'container'}"><button class="gg-icon-button gg-medium-withtext" onclick="${() => this.onLoginClick()}"><img alt="menu-icon" src="icons/user.svg"> <span i18n="Login">Login</span></button></div><div class="${this.state.oauth.status !== 'loggedIn' ? 'hidden' : 'container'}"><button class="gg-icon-button gg-medium-withtext" onclick="${() => this.onLogoutClick()}"><img alt="menu-icon" src="${this.userIconUrl ?? 'icons/user.svg'}" class="${this.userIconUrl ? 'user-avatar' : ''}"><div class="name"><span class="${this.state.oauth.userInfo?.given_name ? '' : 'hidden'}">${this.state.oauth.userInfo?.given_name + ' ' + this.state.oauth.userInfo?.family_name}</span> <span>${this.getDisplayName()}</span></div></button></div></div>
14
+ <div class="${this.context.configManager.Config.oauth || this.context.configManager.Config.gmfauth ? '' : 'hidden'}"><div class="${this.state.oauth.status === 'loggedIn' ? 'hidden' : 'container'}"><button class="gg-icon-button gg-medium-withtext" onclick="${() => this.onLoginClick()}"><img alt="menu-icon" src="icons/user.svg"> <span i18n="Login">Login</span></button></div><div class="${this.state.oauth.status !== 'loggedIn' ? 'hidden' : 'container'}"><button class="gg-icon-button logged-in" onclick="${() => this.onLogoutClick()}"><span class="avatar-initials">${this.getInitials()}</span><div class="name"><span class="${this.getFullName() ? '' : 'hidden'}">${this.getFullName()}</span> <span>${this.getDisplayName()}</span></div></button></div></div>
15
15
  ${this.htmlUnsafe(this.feedbackTemplateHtml ?? '')}`;
16
16
  };
17
- userIconUrl;
18
17
  constructor(name = 'oauth') {
19
18
  super(name);
20
19
  }
@@ -31,11 +30,29 @@ ${this.htmlUnsafe(this.feedbackTemplateHtml ?? '')}`;
31
30
  }
32
31
  return this.state.oauth.userInfo?.email ?? 'Unknown User';
33
32
  }
34
- refreshRender() {
35
- this.getUserIconUrl().then((url) => {
36
- this.userIconUrl = url;
37
- super.refreshRender();
38
- });
33
+ getFullName() {
34
+ if (this.state.oauth.userInfo?.given_name) {
35
+ return `${this.state.oauth.userInfo?.given_name} ${this.state.oauth.userInfo?.family_name}`;
36
+ }
37
+ // No full name
38
+ return null;
39
+ }
40
+ /**
41
+ * Gets the user initials
42
+ * super_admin -> SA
43
+ * Gustave-André de la Girouette -> GG
44
+ * @returns
45
+ */
46
+ getInitials() {
47
+ const name = this.getFullName() ?? this.getDisplayName();
48
+ const parts = name.split(/[\s_-]+/).filter((part) => part.length > 0);
49
+ if (parts.length === 0) {
50
+ return '??';
51
+ }
52
+ else if (parts.length === 1) {
53
+ return parts[0].substring(0, 2).toUpperCase();
54
+ }
55
+ return (parts[0][0] + parts.at(-1)[0]).toUpperCase();
39
56
  }
40
57
  onLoginClick() {
41
58
  if (!this.context.configManager.Config.oauth && !this.context.configManager.Config.gmfauth) {
@@ -51,25 +68,6 @@ ${this.htmlUnsafe(this.feedbackTemplateHtml ?? '')}`;
51
68
  this.context.authManager.logout();
52
69
  }
53
70
  }
54
- async getUserIconUrl() {
55
- if (this.state.oauth.userInfo?.email) {
56
- const hashedEmail = await this.sha256(this.state.oauth.userInfo.email);
57
- const gravatarUrl = `https://www.gravatar.com/avatar/${hashedEmail}?d=mp`;
58
- return gravatarUrl;
59
- }
60
- return undefined;
61
- }
62
- async sha256(message) {
63
- // encode as UTF-8
64
- const msgBuffer = new TextEncoder().encode(message);
65
- // hash the message
66
- const hashBuffer = await crypto.subtle.digest('SHA-256', msgBuffer);
67
- // convert ArrayBuffer to Array
68
- const hashArray = Array.from(new Uint8Array(hashBuffer));
69
- // convert bytes to hex string
70
- const hashHex = hashArray.map((b) => b.toString(16).padStart(2, '0')).join('');
71
- return hashHex;
72
- }
73
71
  connectedCallback() {
74
72
  super.connectedCallback();
75
73
  super.render();
@@ -12,6 +12,20 @@
12
12
  text-align: left;
13
13
  }
14
14
 
15
- .user-avatar {
16
- --svg-filter: none;
15
+ .avatar-initials {
16
+ display: flex;
17
+ align-items: center;
18
+ justify-content: center;
19
+ width: 2rem;
20
+ height: 2rem;
21
+ border-radius: 50%;
22
+ background-color: var(--app-color-grey);
23
+ color: black;
24
+ font-weight: bold;
25
+ }
26
+
27
+ .gg-icon-button.logged-in {
28
+ flex-direction: row;
29
+ gap: 0.5rem;
30
+ padding: 0.5rem;
17
31
  }
@@ -11,7 +11,7 @@ export default class MobileOauthComponent extends OauthComponent {
11
11
  .container{filter:drop-shadow(0 4px 7px #0004382c);background-color:#fff;border-radius:8px;flex-direction:column;padding:1em;display:flex}.container:active{background-color:#c6ceda}.container>button{background:0 0;border:none;flex-direction:column;flex:1;justify-content:center;align-items:center;display:flex}.container>button>img{opacity:.7;width:25%;padding-bottom:1em}.container>button>span{color:#3f68a7;flex:1;justify-content:center;align-items:center;font-size:1.2em;font-weight:600;display:flex}
12
12
  </style>
13
13
  <style>${this.customStyle}</style>
14
- <div class="${this.context.configManager.Config.oauth || this.context.configManager.Config.gmfauth ? '' : 'hidden'}"><div class="${this.state.oauth.status === 'loggedIn' ? 'hidden' : 'container'}"><button class="gg-icon-button gg-medium-withtext" onclick="${() => this.onLoginClick()}"><img alt="menu-icon" src="icons/user.svg"> <span i18n="Login">Login</span></button></div><div class="${this.state.oauth.status !== 'loggedIn' ? 'hidden' : 'container'}"><button class="gg-icon-button gg-medium-withtext" onclick="${() => this.onLogoutClick()}"><img alt="menu-icon" src="${this.userIconUrl ?? 'icons/user.svg'}" class="${this.userIconUrl ? 'user-avatar' : ''}"><div class="name"><span class="${this.state.oauth.userInfo?.given_name ? '' : 'hidden'}">${this.state.oauth.userInfo?.given_name + ' ' + this.state.oauth.userInfo?.family_name}</span> <span>${this.getDisplayName()}</span></div></button></div></div>
14
+ <div class="${this.context.configManager.Config.oauth || this.context.configManager.Config.gmfauth ? '' : 'hidden'}"><div class="${this.state.oauth.status === 'loggedIn' ? 'hidden' : 'container'}"><button class="gg-icon-button gg-medium-withtext" onclick="${() => this.onLoginClick()}"><img alt="menu-icon" src="icons/user.svg"> <span i18n="Login">Login</span></button></div><div class="${this.state.oauth.status !== 'loggedIn' ? 'hidden' : 'container'}"><button class="gg-icon-button logged-in" onclick="${() => this.onLogoutClick()}"><span class="avatar-initials">${this.getInitials()}</span><div class="name"><span class="${this.getFullName() ? '' : 'hidden'}">${this.getFullName()}</span> <span>${this.getDisplayName()}</span></div></button></div></div>
15
15
  ${this.htmlUnsafe(this.feedbackTemplateHtml ?? '')}`;
16
16
  };
17
17
  constructor() {
@@ -47,9 +47,12 @@ ${this.htmlUnsafe(this.feedbackTemplateHtml ?? '')}`;
47
47
  if (!this.state.interface.basemapComponentVisible) {
48
48
  this.hideHelpFor('basemap');
49
49
  }
50
- if (!this.state.interface.searchComponentVisible) {
50
+ if (!this.state.interface.searchComponentVisible || !this.context.configManager.Config.search) {
51
51
  this.hideHelpFor('search');
52
52
  }
53
+ if (!this.context.configManager.Config.onboarding) {
54
+ this.hideHelpFor('onboarding');
55
+ }
53
56
  }
54
57
  hideHelpFor(elementId) {
55
58
  this.shadow.querySelector(`#${elementId}`).style.display = 'none';
@@ -75,7 +75,7 @@ ${this.htmlUnsafe(this.feedbackTemplateHtml ?? '')}`;
75
75
  }
76
76
  }
77
77
  createPreviewLayer() {
78
- this.paintSearchResults = this.context.configManager.Config.search.paintSearchResults;
78
+ this.paintSearchResults = this.context.configManager.Config.search?.paintSearchResults;
79
79
  this.previewGeoLayer = new VectorLayer({
80
80
  properties: {
81
81
  addToPrintedLayers: true
@@ -115,8 +115,8 @@ ${this.htmlUnsafe(this.feedbackTemplateHtml ?? '')}`;
115
115
  }
116
116
  connectedCallback() {
117
117
  super.connectedCallback();
118
- this.defaultSearchStrokeColor = this.context.configManager.Config.search.defaultStrokeColor;
119
- this.defaultSearchFillColor = this.context.configManager.Config.search.defaultFillColor;
118
+ this.defaultSearchStrokeColor = this.context.configManager.Config.search?.defaultStrokeColor;
119
+ this.defaultSearchFillColor = this.context.configManager.Config.search?.defaultFillColor;
120
120
  this.createPreviewLayer();
121
121
  this.render();
122
122
  super.girafeTranslate();
@@ -260,13 +260,13 @@ ${this.htmlUnsafe(this.feedbackTemplateHtml ?? '')}`;
260
260
  resultHtmlElement.scrollIntoView({ block: 'nearest' });
261
261
  }
262
262
  preview(result) {
263
- if (result.getGeometry() && this.context.configManager.Config.search.objectPreview) {
263
+ if (result.getGeometry() && this.context.configManager.Config.search?.objectPreview) {
264
264
  // Result with geometry
265
265
  this.addFeatureToPreview(result);
266
266
  this.updatePreviewLayerStyle();
267
267
  }
268
268
  const firstAction = result.get('actions')?.[0];
269
- if (firstAction?.action.startsWith('add_layer') && this.context.configManager.Config.search.layerPreview) {
269
+ if (firstAction?.action.startsWith('add_layer') && this.context.configManager.Config.search?.layerPreview) {
270
270
  const layer = this.context.themesHelper.findLayerByName(firstAction.data);
271
271
  if (layer) {
272
272
  const clonedTheme = this.context.themesHelper.getMinimalClonedThemeForLayer(layer);
@@ -348,7 +348,7 @@ ${this.htmlUnsafe(this.feedbackTemplateHtml ?? '')}`;
348
348
  }
349
349
  }
350
350
  zoomTo(extent) {
351
- const minResolution = this.context.configManager.Config.search.minResolution;
351
+ const minResolution = this.context.configManager.Config.search?.minResolution;
352
352
  const currentResolution = this.map.getView().getResolution();
353
353
  const currentExtent = this.map.getView().calculateExtent();
354
354
  if (minResolution) {
@@ -453,7 +453,7 @@ ${this.htmlUnsafe(this.feedbackTemplateHtml ?? '')}`;
453
453
  if (colorPicker) {
454
454
  this.girafeColorPicker = new GirafeColorPicker({
455
455
  parent: colorPicker,
456
- color: this.context.configManager.Config.search.defaultStrokeColor,
456
+ color: this.context.configManager.Config.search?.defaultStrokeColor,
457
457
  popup: 'right'
458
458
  });
459
459
  this.girafeColorPicker.onChange = (color) => {
@@ -465,7 +465,7 @@ ${this.htmlUnsafe(this.feedbackTemplateHtml ?? '')}`;
465
465
  };
466
466
  this.girafeColorPicker.onOpen = (_) => {
467
467
  if (this.defaultColorHasChanged()) {
468
- this.girafeColorPicker?.setColor(this.context.configManager.Config.search.defaultStrokeColor, true);
468
+ this.girafeColorPicker?.setColor(this.context.configManager.Config.search?.defaultStrokeColor, true);
469
469
  }
470
470
  };
471
471
  }
@@ -476,10 +476,10 @@ ${this.htmlUnsafe(this.feedbackTemplateHtml ?? '')}`;
476
476
  }
477
477
  // Only update style if new colors were provided via color picker or default colors have changed
478
478
  if (this.defaultColorHasChanged()) {
479
- const defaultFillColor = this.context.configManager.Config.search.defaultFillColor;
479
+ const defaultFillColor = this.context.configManager.Config.search?.defaultFillColor;
480
480
  this.defaultSearchFillColor = defaultFillColor;
481
481
  this.searchFillColor = defaultFillColor;
482
- const defaultStrokeColor = this.context.configManager.Config.search.defaultStrokeColor;
482
+ const defaultStrokeColor = this.context.configManager.Config.search?.defaultStrokeColor;
483
483
  this.defaultSearchStrokeColor = defaultStrokeColor;
484
484
  this.searchStrokeColor = defaultStrokeColor;
485
485
  }
@@ -488,7 +488,7 @@ ${this.htmlUnsafe(this.feedbackTemplateHtml ?? '')}`;
488
488
  this.previewGeoLayer.setStyle(new Style({
489
489
  stroke: new Stroke({
490
490
  color: strokeColorWithFallback,
491
- width: this.context.configManager.Config.search.defaultStrokeWidth
491
+ width: this.context.configManager.Config.search?.defaultStrokeWidth
492
492
  }),
493
493
  fill: new Fill({ color: fillColorWithFallback }),
494
494
  image: new Icon({
@@ -501,8 +501,8 @@ ${this.htmlUnsafe(this.feedbackTemplateHtml ?? '')}`;
501
501
  }));
502
502
  }
503
503
  defaultColorHasChanged() {
504
- return (this.defaultSearchFillColor !== this.context.configManager.Config.search.defaultFillColor ||
505
- this.defaultSearchStrokeColor !== this.context.configManager.Config.search.defaultStrokeColor);
504
+ return (this.defaultSearchFillColor !== this.context.configManager.Config.search?.defaultFillColor ||
505
+ this.defaultSearchStrokeColor !== this.context.configManager.Config.search?.defaultStrokeColor);
506
506
  }
507
507
  getColoredPinIcon(hexColor) {
508
508
  const pin = `<svg xmlns="http://www.w3.org/2000/svg"
@@ -65,6 +65,13 @@ ${this.htmlUnsafe(this.feedbackTemplateHtml ?? '')}`;
65
65
  drawingFillColor: new UserPreference('drawing.defaultFillColor', null, 'visual', 'color'),
66
66
  drawingStrokeColor: new UserPreference('drawing.defaultStrokeColor', null, 'visual', 'color')
67
67
  };
68
+ // Remove preferences where no config entry exists (e.g. when the component isn't configured for this app instance)
69
+ for (const key in this.preferences) {
70
+ const configEntry = getPropertyByPath(this.context.configManager.Config, this.preferences[key].configPath);
71
+ if (!configEntry.found) {
72
+ delete this.preferences[key];
73
+ }
74
+ }
68
75
  this.subscribe('themes.isLoaded', (_, isLoaded) => {
69
76
  if (isLoaded) {
70
77
  this.initPreferenceOptions();
@@ -120,42 +127,59 @@ ${this.htmlUnsafe(this.feedbackTemplateHtml ?? '')}`;
120
127
  For preferences that have options to choose from, readout all possible select options to show in the dropdowns
121
128
  */
122
129
  initPreferenceOptions() {
123
- this.preferences.language.options = Object.keys(this.context.configManager.Config.languages.translations).map((key) => {
124
- return { label: key, value: key };
125
- });
126
- this.preferences.logLevel.options = [
127
- { label: 'debug', value: 'debug' },
128
- { label: 'info', value: 'info' },
129
- { label: 'warn', value: 'warn' },
130
- { label: 'error', value: 'error' }
131
- ];
132
- this.preferences.projection.options = Object.keys(this.context.configManager.Config.projections).map((key) => {
133
- return { label: this.context.configManager.Config.projections[key], value: key };
134
- });
130
+ if (this.preferences.language) {
131
+ this.preferences.language.options = Object.keys(this.context.configManager.Config.languages.translations).map((key) => {
132
+ return { label: key, value: key };
133
+ });
134
+ }
135
+ if (this.preferences.logLevel) {
136
+ this.preferences.logLevel.options = [
137
+ { label: 'debug', value: 'debug' },
138
+ { label: 'info', value: 'info' },
139
+ { label: 'warn', value: 'warn' },
140
+ { label: 'error', value: 'error' }
141
+ ];
142
+ }
143
+ if (this.preferences.projection) {
144
+ this.preferences.projection.options = Object.keys(this.context.configManager.Config.projections).map((key) => {
145
+ return { label: this.context.configManager.Config.projections[key], value: key };
146
+ });
147
+ }
135
148
  this.refreshThemeOptions();
136
- this.preferences.basemap.options = Object.keys(this.state.basemaps).map((key) => {
137
- const baseMapName = this.state.basemaps[Number(key)].name;
138
- return { label: baseMapName, value: baseMapName };
139
- });
140
- this.preferences.selectionComponent.options = [
141
- { label: 'window', value: 'window' },
142
- { label: 'grid', value: 'grid' }
143
- ];
144
- this.preferences.darkFrontendMode.options = [
145
- { label: 'same as system', value: undefined },
146
- { label: 'dark', value: true },
147
- { label: 'light', value: false }
148
- ];
149
- this.preferences.selectHighlightMode.options = [
150
- { label: 'highlight-all', value: HighlightMode.HighlightAll },
151
- { label: 'highlight-per-layer', value: HighlightMode.HighlightPerLayer }
152
- ];
149
+ if (this.preferences.basemap) {
150
+ this.preferences.basemap.options = Object.keys(this.state.basemaps).map((key) => {
151
+ const baseMapName = this.state.basemaps[Number(key)].name;
152
+ return { label: baseMapName, value: baseMapName };
153
+ });
154
+ }
155
+ if (this.preferences.selectionComponent) {
156
+ this.preferences.selectionComponent.options = [
157
+ { label: 'window', value: 'window' },
158
+ { label: 'grid', value: 'grid' }
159
+ ];
160
+ }
161
+ if (this.preferences.darkFrontendMode) {
162
+ this.preferences.darkFrontendMode.options = [
163
+ { label: 'same as system', value: undefined },
164
+ { label: 'dark', value: true },
165
+ { label: 'light', value: false }
166
+ ];
167
+ }
168
+ if (this.preferences.selectHighlightMode) {
169
+ this.preferences.selectHighlightMode.options = [
170
+ { label: 'highlight-all', value: HighlightMode.HighlightAll },
171
+ { label: 'highlight-per-layer', value: HighlightMode.HighlightPerLayer }
172
+ ];
173
+ }
153
174
  }
154
175
  /**
155
176
  * Collect all possible options for the default theme preference. This has to be repeatable, since the user
156
177
  * can create new custom themes at any time.
157
178
  */
158
179
  refreshThemeOptions() {
180
+ if (!this.preferences.theme) {
181
+ return;
182
+ }
159
183
  let defaultThemes = [{ label: '-', value: '' }];
160
184
  Object.keys(this.state.themes._allThemes).forEach((key) => {
161
185
  const themeName = this.state.themes._allThemes[Number(key)].name;
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.2666688189",
8
+ "version": "1.2.0-dev.2675426009",
9
9
  "type": "module",
10
10
  "engines": {
11
11
  "node": ">=20.19.0"
package/styles/index.css CHANGED
@@ -25,6 +25,11 @@ header {
25
25
  justify-content: space-between;
26
26
  }
27
27
 
28
+ .header-center-align {
29
+ margin-left: auto;
30
+ margin-right: auto;
31
+ }
32
+
28
33
  img {
29
34
  filter: var(--svg-filter);
30
35
  }
@@ -42,8 +47,6 @@ girafe-theme-select {
42
47
 
43
48
  girafe-search {
44
49
  width: 36rem;
45
- margin-left: auto;
46
- margin-right: auto;
47
50
  }
48
51
 
49
52
  #buttonbar {
@@ -115,13 +115,9 @@
115
115
  <header>
116
116
  <girafe-theme-select data-tour="theme-selection"></girafe-theme-select>
117
117
  <img class="logo" alt="geogirafe-logo" src="images/logo/horizontal_black.svg" />
118
- <girafe-search id="search">
119
- <!-- <div slot="search-tooltip">
120
- <p>
121
- Create a custom tooltip
122
- </p>
123
- </div> -->
124
- </girafe-search>
118
+ <div class="header-center-align">
119
+ <girafe-search id="search" plugin="config:search"></girafe-search>
120
+ </div>
125
121
  <girafe-news-button plugin="config:news"></girafe-news-button>
126
122
  <girafe-oauth></girafe-oauth>
127
123
  <girafe-add-bookmark></girafe-add-bookmark>
@@ -1 +1 @@
1
- {"version":"1.2.0-dev.2666688189", "build":"2666688189", "date":"10/07/2026"}
1
+ {"version":"1.2.0-dev.2675426009", "build":"2675426009", "date":"14/07/2026"}
@@ -15,9 +15,6 @@
15
15
  "url": "mock/themes.json",
16
16
  "defaultTheme": ""
17
17
  },
18
- "search": {
19
- "providers": []
20
- },
21
18
  "basemaps": {
22
19
  "defaultBasemap": "OpenStreetMap",
23
20
  "OSM": true
@@ -42,7 +42,7 @@ declare class GirafeConfig {
42
42
  height: number;
43
43
  };
44
44
  };
45
- search: {
45
+ search?: {
46
46
  providers: ({
47
47
  type: 'geomapfish';
48
48
  url: string;
@@ -175,6 +175,9 @@ class GirafeConfig {
175
175
  };
176
176
  }
177
177
  initConfigShare(config) {
178
+ if (!config.share) {
179
+ return undefined;
180
+ }
178
181
  if (!config.share?.createUrl) {
179
182
  throw new Error(`Configuration for share.createUrl is required. See https://doc.geogirafe.org/docs/configuration`);
180
183
  }
@@ -201,6 +204,9 @@ class GirafeConfig {
201
204
  };
202
205
  }
203
206
  initConfigPrint(config) {
207
+ if (!config.print) {
208
+ return undefined;
209
+ }
204
210
  if (!config.print?.url) {
205
211
  throw new Error(`Configuration for print.url is required. See https://doc.geogirafe.org/docs/configuration`);
206
212
  }
@@ -210,7 +216,7 @@ class GirafeConfig {
210
216
  }
211
217
  initConfigSearch(config) {
212
218
  if (!config.search) {
213
- throw new Error('Configuration for search is required. See https://doc.geogirafe.org/docs/configuration');
219
+ return undefined;
214
220
  }
215
221
  const providers = [];
216
222
  for (const provider of config.search.providers ?? []) {
@@ -237,15 +243,18 @@ class GirafeConfig {
237
243
  console.warn(`Provider type is missing or not valid: ${provider['type']}`);
238
244
  }
239
245
  }
246
+ if (providers.length === 0) {
247
+ console.warn('No search provider configured. Search will not be available.');
248
+ }
240
249
  return {
241
250
  providers: providers,
242
251
  objectPreview: config.search.objectPreview ?? false,
243
252
  layerPreview: config.search.layerPreview ?? false,
244
253
  minResolution: config.search.minResolution ?? 0.5,
245
- defaultFillColor: config.search?.defaultFillColor ?? '#3388ff7f',
246
- defaultStrokeColor: config.search?.defaultStrokeColor ?? '#3388ff',
247
- defaultStrokeWidth: config.search?.defaultStrokeWidth ?? 2,
248
- paintSearchResults: config.search?.paintSearchResults ?? true
254
+ defaultFillColor: config.search.defaultFillColor ?? '#3388ff7f',
255
+ defaultStrokeColor: config.search.defaultStrokeColor ?? '#3388ff',
256
+ defaultStrokeWidth: config.search.defaultStrokeWidth ?? 2,
257
+ paintSearchResults: config.search.paintSearchResults ?? true
249
258
  };
250
259
  }
251
260
  initConfigTreeview(config) {
@@ -271,18 +280,14 @@ class GirafeConfig {
271
280
  return config.lidar;
272
281
  }
273
282
  initConfigNews(config) {
274
- if (config.news?.sanitizeConfig) {
275
- return config.news;
283
+ if (!config.news) {
284
+ return undefined;
276
285
  }
277
- else if (config.news) {
278
- return {
279
- ...config.news,
280
- sanitizeConfig: defaultNewsSanitizeConfig
281
- };
286
+ if (config.news.sanitizeConfig) {
287
+ return config.news;
282
288
  }
283
289
  return {
284
- urls: [],
285
- autoDisplay: false,
290
+ ...config.news,
286
291
  sanitizeConfig: defaultNewsSanitizeConfig
287
292
  };
288
293
  }
@@ -639,7 +639,7 @@ export default class OlDrawing {
639
639
  const olFeature = this.getOlFeatureFromDrawingSource(drawingFeature.id);
640
640
  const extent = olFeature?.getGeometry()?.getExtent();
641
641
  if (extent) {
642
- const minResolution = this.config.search.minResolution;
642
+ const minResolution = this.config.search?.minResolution ?? 0.5;
643
643
  this.context.mapManager.zoomToExtent(extent, minResolution);
644
644
  }
645
645
  }
@@ -18,7 +18,7 @@ class SearchManager extends GirafeSingleton {
18
18
  }
19
19
  initializeSingleton() {
20
20
  super.initializeSingleton();
21
- for (const providerConfig of this.context.configManager.Config.search.providers ?? []) {
21
+ for (const providerConfig of this.context.configManager.Config.search?.providers ?? []) {
22
22
  this.createClient(providerConfig);
23
23
  }
24
24
  this.defaultSrid = this.context.configManager.getDefaultConfigValue('map.srid');