@geogirafe/lib-geoportal 1.2.0-dev.2847697588 → 1.2.0-dev.2854703696

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. package/assets/i18n/de.json +1 -0
  2. package/assets/i18n/en.json +1 -0
  3. package/assets/i18n/fr.json +1 -0
  4. package/assets/i18n/it.json +1 -0
  5. package/components/advancedfilter/component.d.ts +2 -1
  6. package/components/advancedfilter/component.js +3 -2
  7. package/components/auth/component.js +2 -2
  8. package/components/auth/style.css +10 -0
  9. package/components/auth-mobile/component.js +1 -1
  10. package/components/header/component.d.ts +15 -0
  11. package/components/header/component.js +67 -0
  12. package/components/header/style.css +38 -0
  13. package/components/main.d.ts +1 -0
  14. package/components/main.js +1 -0
  15. package/components/prototypebanner/component.js +6 -1
  16. package/components/themes/component.d.ts +2 -2
  17. package/components/themes-mobile/theme/component.js +3 -2
  18. package/components/userpreferences/component.js +5 -10
  19. package/models/basemaps/basemap.d.ts +3 -3
  20. package/models/customtheme.d.ts +2 -1
  21. package/models/georama.d.ts +10 -0
  22. package/models/georama.js +1 -0
  23. package/models/gmf.d.ts +0 -1
  24. package/models/layerdefinitions.d.ts +13 -0
  25. package/models/layerdefinitions.js +1 -0
  26. package/models/layers/baselayer.d.ts +3 -2
  27. package/models/layers/grouplayer.d.ts +2 -1
  28. package/models/layers/layer.d.ts +2 -1
  29. package/models/layers/layercog.d.ts +4 -4
  30. package/models/layers/layercog.js +3 -3
  31. package/models/layers/layerlocalfile.d.ts +2 -1
  32. package/models/layers/layervectortiles.d.ts +2 -1
  33. package/models/layers/layerwms.d.ts +4 -4
  34. package/models/layers/layerwms.js +3 -3
  35. package/models/layers/layerwmts.d.ts +4 -4
  36. package/models/layers/layerwmts.js +3 -3
  37. package/models/layers/layerxyz.d.ts +4 -4
  38. package/models/layers/layerxyz.js +3 -3
  39. package/models/layers/themelayer.d.ts +2 -1
  40. package/models/main.d.ts +3 -1
  41. package/package.json +1 -1
  42. package/styles/iframe.css +6 -5
  43. package/styles/index.css +2 -4
  44. package/templates/iframe.html +2 -2
  45. package/templates/index.html +2 -2
  46. package/templates/mobile.html +2 -1
  47. package/templates/public/about.json +1 -1
  48. package/tools/app/geogirafeapp-mobile.js +2 -0
  49. package/tools/app/geogirafeapp.js +2 -0
  50. package/tools/configuration/girafeconfig.d.ts +9 -0
  51. package/tools/configuration/girafeconfig.js +14 -0
  52. package/tools/share/serializers/sharedtypes.d.ts +6 -5
  53. package/tools/state/state.d.ts +4 -3
  54. package/tools/themes/themeshelper.d.ts +3 -2
  55. package/tools/themes/themesmanager.js +1 -1
@@ -247,6 +247,7 @@
247
247
  "geoadminch_other": "Andere",
248
248
  "geoadminch_parcel": "Parzellen",
249
249
  "geoadminch_zipcode": "PLZ",
250
+ "GeoGirafe is a flexible application to build online geoportals.": "GeoGirafe ist eine flexible Applikation um Online-Geoportale zu erstellen.",
250
251
  "GeoJSON": "GeoJSON",
251
252
  "Geolocation browser error": "Ihr Browser unterstützt keine Geolokalisierung",
252
253
  "Geolocation error": "Geolocation-Fehler",
@@ -249,6 +249,7 @@
249
249
  "geoadminch_other": "Other",
250
250
  "geoadminch_parcel": "Parcels",
251
251
  "geoadminch_zipcode": "ZIP codes",
252
+ "GeoGirafe is a flexible application to build online geoportals.": "GeoGirafe is a flexible application to build online geoportals.",
252
253
  "GeoJSON": "GeoJSON",
253
254
  "Geolocation browser error": "Your browser does not support geolocation",
254
255
  "Geolocation error": "Geolocation error",
@@ -247,6 +247,7 @@
247
247
  "geoadminch_other": "Autres",
248
248
  "geoadminch_parcel": "Parcelles",
249
249
  "geoadminch_zipcode": "Codes postaux",
250
+ "GeoGirafe is a flexible application to build online geoportals.": "GeoGirafe est une application flexible permettant de créer des géoportails en ligne.",
250
251
  "GeoJSON": "GeoJSON",
251
252
  "Geolocation browser error": "Votre navigateur ne supporte pas la géolocalisation",
252
253
  "Geolocation error": "Erreur de géolocalisation",
@@ -247,6 +247,7 @@
247
247
  "geoadminch_other": "Altri",
248
248
  "geoadminch_parcel": "Parcelle",
249
249
  "geoadminch_zipcode": "Codici postali",
250
+ "GeoGirafe is a flexible application to build online geoportals.": "GeoGirafe è un'applicazione flessibile per creare geoportali online.",
250
251
  "GeoJSON": "GeoJSON",
251
252
  "Geolocation browser error": "Il tuo browser non supporta la geolocalizzazione",
252
253
  "Geolocation error": "Errore di geolocalizzazione",
@@ -4,6 +4,7 @@ import ColumnAliasHelper from '../../tools/utils/aliases.js';
4
4
  import IGirafePanel from '../../tools/state/igirafepanel.js';
5
5
  import LayerFilterElement from './layerfilterelement.js';
6
6
  import FilterConditionElement from './filterconditionelement.js';
7
+ import { LayerId } from '../../models/layerdefinitions.js';
7
8
  declare class AdvancedFilterComponent extends GirafeHTMLElement implements IGirafePanel {
8
9
  protected templateUrl: string | null;
9
10
  protected styleUrls: string[] | null;
@@ -16,7 +17,7 @@ declare class AdvancedFilterComponent extends GirafeHTMLElement implements IGira
16
17
  layers: LayerWms[];
17
18
  layerFilterElements: LayerFilterElement[];
18
19
  columnAliasHelper: ColumnAliasHelper;
19
- layerIsRefreshing: Map<number, boolean>;
20
+ layerIsRefreshing: Map<LayerId, boolean>;
20
21
  private csvDownloader;
21
22
  constructor();
22
23
  render(): void;
@@ -185,8 +185,9 @@ ${this.htmlUnsafe(this.feedbackTemplateHtml ?? '')}`;
185
185
  filterElement.addCondition();
186
186
  this.render();
187
187
  }
188
- const layerId = Number(e.target.value);
189
- const layer = this.layers.find((l) => l.id === layerId);
188
+ const layerId = e.target.value;
189
+ // Loose equality == is on purpose. layer.id can be of type number or string, but .value always returns a string
190
+ const layer = this.layers.find((l) => l.id == layerId);
190
191
  if (filterElement.hasAppliedConditions()) {
191
192
  // If the filter has already been applied to the layer, a new filter is created and the old one is deleted
192
193
  const newFilter = new LayerFilterElement(this.context, this.columnAliasHelper, this.layerFilterElements.length);
@@ -8,10 +8,10 @@ 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);cursor:pointer;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}.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}
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}@media (width<=991px){#login-btn>span,.logged-in>.name{display:none}#login-btn>img{padding-right:.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 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>
14
+ <div class="${this.context.configManager.Config.oauth || this.context.configManager.Config.gmfauth ? '' : 'hidden'}"><div class="${this.state.oauth.status === 'loggedIn' ? 'hidden' : 'container'}"><button id="login-btn" class="gg-icon-button gg-medium-withtext" aria-label="Login" 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(name = 'oauth') {
@@ -29,3 +29,13 @@
29
29
  gap: 0.5rem;
30
30
  padding: 0.5rem;
31
31
  }
32
+
33
+ @media (max-width: 991px) {
34
+ #login-btn > span,
35
+ .logged-in > .name {
36
+ display: none;
37
+ }
38
+ #login-btn > img {
39
+ padding-right: 0.5rem;
40
+ }
41
+ }
@@ -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 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>
14
+ <div class="${this.context.configManager.Config.oauth || this.context.configManager.Config.gmfauth ? '' : 'hidden'}"><div class="${this.state.oauth.status === 'loggedIn' ? 'hidden' : 'container'}"><button id="login-btn" class="gg-icon-button gg-medium-withtext" aria-label="Login" 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() {
@@ -0,0 +1,15 @@
1
+ import GirafeHTMLElement from '../../base/GirafeHTMLElement.js';
2
+ import GirafeConfig from '../../tools/configuration/girafeconfig.js';
3
+ declare class HeaderComponent extends GirafeHTMLElement {
4
+ protected templateUrl: string | null;
5
+ protected styleUrls: string[] | null;
6
+ template: () => import("uhtml").Hole;
7
+ constructor();
8
+ get config(): GirafeConfig;
9
+ get logoSource(): string;
10
+ get logoAltText(): string;
11
+ private setHtmlMetaData;
12
+ registerEvents(): void;
13
+ protected connectedCallback(): void;
14
+ }
15
+ export default HeaderComponent;
@@ -0,0 +1,67 @@
1
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
2
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
4
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
6
+ };
7
+ import { html as uHtml } from 'uhtml';
8
+ // SPDX-License-Identifier: Apache-2.0
9
+ import GirafeHTMLElement from '../../base/GirafeHTMLElement.js';
10
+ import { UsedInTemplateOnly } from '../../decorators.js';
11
+ class HeaderComponent extends GirafeHTMLElement {
12
+ templateUrl = null;
13
+ styleUrls = null;
14
+ template = () => {
15
+ return uHtml `<style>
16
+ #header{box-sizing:border-box;align-items:center;gap:1rem;height:100%;padding:.15rem 1rem;display:flex}#logo{filter:none;width:auto;height:100%;max-height:100%;display:block}#app-title{color:var(--text-color);-webkit-line-clamp:2;-webkit-box-orient:vertical;min-width:0;font-size:1.3rem;font-weight:700;display:-webkit-box;overflow:hidden}@media (width<=991px){#app-title{display:none}}
17
+ </style>
18
+ <style>${this.customStyle}</style>
19
+ <div id="header">${this.config.branding.logo !== '' ? uHtml ` <img id="logo" alt="${this.logoAltText}" src="${this.logoSource}"> ` : ''} ${this.config.branding.showAppTitleInHeader ? uHtml `<div id="app-title" i18n="${this.config.branding.appTitle}">${this.config.branding.appTitle}</div>` : ''}</div>
20
+ ${this.htmlUnsafe(this.feedbackTemplateHtml ?? '')}`;
21
+ };
22
+ constructor() {
23
+ super('header');
24
+ }
25
+ get config() {
26
+ return this.context.configManager.Config;
27
+ }
28
+ get logoSource() {
29
+ if (this.state.interface.darkFrontendMode) {
30
+ return this.config.branding.logoDarkMode;
31
+ }
32
+ return this.config.branding.logo;
33
+ }
34
+ get logoAltText() {
35
+ return `${this.config.branding.appTitle.toLowerCase().replaceAll(' ', '-')}-logo`;
36
+ }
37
+ setHtmlMetaData() {
38
+ const title = this.context.i18nManager.getTranslation(this.config.branding.appTitle);
39
+ const description = this.context.i18nManager.getTranslation(this.config.branding.description);
40
+ document.title = title;
41
+ document.querySelector('meta[name="description"]')?.setAttribute('content', description);
42
+ }
43
+ registerEvents() {
44
+ this.subscribe('language', () => this.setHtmlMetaData());
45
+ if (!this.state.interface.isMobile) {
46
+ this.subscribe('interface.darkFrontendMode', () => this.refreshRender());
47
+ }
48
+ }
49
+ connectedCallback() {
50
+ super.connectedCallback();
51
+ this.setHtmlMetaData();
52
+ this.render();
53
+ super.girafeTranslate();
54
+ if (this.state.interface.isMobile) {
55
+ // Header shouldn't be visible on mobile
56
+ this.hide();
57
+ }
58
+ this.registerEvents();
59
+ }
60
+ }
61
+ __decorate([
62
+ UsedInTemplateOnly()
63
+ ], HeaderComponent.prototype, "logoSource", null);
64
+ __decorate([
65
+ UsedInTemplateOnly()
66
+ ], HeaderComponent.prototype, "logoAltText", null);
67
+ export default HeaderComponent;
@@ -0,0 +1,38 @@
1
+ /* SPDX-License-Identifier: Apache-2.0 */
2
+ #header {
3
+ height: 100%;
4
+ display: flex;
5
+ gap: 1rem;
6
+ padding: 0.15rem 1rem;
7
+ box-sizing: border-box;
8
+ align-items: center;
9
+ }
10
+
11
+ #logo {
12
+ width: auto;
13
+ height: 100%;
14
+ max-height: 100%;
15
+ display: block;
16
+ filter: none;
17
+ }
18
+
19
+ #app-title {
20
+ font-size: 1.3rem;
21
+ font-weight: bold;
22
+ color: var(--text-color);
23
+ min-width: 0;
24
+ /* Will limit word wrapping to two lines */
25
+ display: -webkit-box;
26
+ -webkit-box-orient: vertical;
27
+ -webkit-line-clamp: 2;
28
+ overflow: hidden;
29
+ }
30
+
31
+ /*
32
+ Don't show app title on small screens
33
+ */
34
+ @media (max-width: 991px) {
35
+ #app-title {
36
+ display: none;
37
+ }
38
+ }
@@ -24,6 +24,7 @@ export { default as ExternalLayersComponent } from './extlayers/component.js';
24
24
  export { default as FeedbackButtonComponent } from './feedbackbutton/component.js';
25
25
  export { default as GeolocationMobile } from './geolocation-mobile/component.js';
26
26
  export { default as GetDirectionsArtifact } from './getdirections/component.js';
27
+ export { default as HeaderComponent } from './header/component.js';
27
28
  export { default as HelpComponent } from './help/component.js';
28
29
  export { default as InfoboxComponent } from './infobox/component.js';
29
30
  export { default as InfoWindowComponent } from './infowindow/component.js';
@@ -23,6 +23,7 @@ export { default as ExternalLayersComponent } from './extlayers/component.js';
23
23
  export { default as FeedbackButtonComponent } from './feedbackbutton/component.js';
24
24
  export { default as GeolocationMobile } from './geolocation-mobile/component.js';
25
25
  export { default as GetDirectionsArtifact } from './getdirections/component.js';
26
+ export { default as HeaderComponent } from './header/component.js';
26
27
  export { default as HelpComponent } from './help/component.js';
27
28
  export { default as InfoboxComponent } from './infobox/component.js';
28
29
  export { default as InfoWindowComponent } from './infowindow/component.js';
@@ -19,7 +19,12 @@ ${this.htmlUnsafe(this.feedbackTemplateHtml ?? '')}`;
19
19
  }
20
20
  connectedCallback() {
21
21
  super.connectedCallback();
22
- this.render();
22
+ if (this.context.configManager.Config.branding.prototypeBanner) {
23
+ this.render();
24
+ }
25
+ else {
26
+ this.hide();
27
+ }
23
28
  }
24
29
  }
25
30
  export default PrototypeBannerComponent;
@@ -13,9 +13,9 @@ declare class ThemeComponent extends GirafeHTMLElement {
13
13
  activeThemeType: ThemeType;
14
14
  clickOutsideContainer: HTMLElement | null;
15
15
  get customThemes(): CustomTheme[];
16
- get allThemes(): Record<number, ThemeLayer>;
16
+ get allThemes(): Record<import("../../models/layerdefinitions.js").LayerId, ThemeLayer>;
17
17
  get favoriteThemes(): (ThemeLayer | CustomTheme)[];
18
- get activeThemes(): Record<number, ThemeLayer> | (ThemeLayer | CustomTheme)[];
18
+ get activeThemes(): Record<import("../../models/layerdefinitions.js").LayerId, ThemeLayer> | (ThemeLayer | CustomTheme)[];
19
19
  constructor();
20
20
  registerEvents(): void;
21
21
  render(): void;
@@ -28,8 +28,9 @@ ${this.htmlUnsafe(this.feedbackTemplateHtml ?? '')}`;
28
28
  this.refreshTheme();
29
29
  }
30
30
  refreshTheme() {
31
- const themeId = Number(this.getAttribute('themeid'));
32
- const theme = this.state.layers.layersList.find((layer) => layer.id === themeId);
31
+ const themeId = this.getAttribute('themeid');
32
+ // Loose equality == is on purpose. layer.id can be of type number or string, but getAttribute() always returns a string
33
+ const theme = this.state.layers.layersList.find((layer) => layer.id == themeId);
33
34
  if (theme) {
34
35
  this.theme = this.context.layerManager.getTreeItem(theme.treeItemId);
35
36
  }
@@ -47,10 +47,7 @@ ${this.htmlUnsafe(this.feedbackTemplateHtml ?? '')}`;
47
47
  }
48
48
  }),
49
49
  logLevel: new UserPreference('general.logLevel', null, 'system', 'select'),
50
- theme: new UserPreference('themes.defaultTheme', 'theme', 'map', 'select', (themeName) => {
51
- const themeId = Object.keys(this.state.themes._allThemes).find((themeKey) => this.state.themes._allThemes[Number(themeKey)].name === themeName);
52
- return this.state.themes._allThemes[Number(themeId)];
53
- }),
50
+ theme: new UserPreference('themes.defaultTheme', 'theme', 'map', 'select', (themeName) => Object.values(this.state.themes._allThemes).find((theme) => theme.name === themeName)),
54
51
  basemap: new UserPreference('basemaps.defaultBasemap', 'activeBasemaps', 'map', 'select', (bsName) => {
55
52
  const newDefaultBasemap = Object.values(this.state.basemaps).find((basemap) => basemap.name === bsName);
56
53
  if (newDefaultBasemap?.opacity == 0) {
@@ -151,9 +148,8 @@ ${this.htmlUnsafe(this.feedbackTemplateHtml ?? '')}`;
151
148
  }
152
149
  this.refreshThemeOptions();
153
150
  if (this.preferences.basemap) {
154
- this.preferences.basemap.options = Object.keys(this.state.basemaps).map((key) => {
155
- const baseMapName = this.state.basemaps[Number(key)].name;
156
- return { label: baseMapName, value: baseMapName };
151
+ this.preferences.basemap.options = Object.values(this.state.basemaps).map((basemap) => {
152
+ return { label: basemap.name, value: basemap.name };
157
153
  });
158
154
  }
159
155
  if (this.preferences.selectionComponent) {
@@ -198,9 +194,8 @@ ${this.htmlUnsafe(this.feedbackTemplateHtml ?? '')}`;
198
194
  return;
199
195
  }
200
196
  let defaultThemes = [{ label: '-', value: '' }];
201
- Object.keys(this.state.themes._allThemes).forEach((key) => {
202
- const themeName = this.state.themes._allThemes[Number(key)].name;
203
- defaultThemes.push({ label: themeName, value: themeName });
197
+ Object.values(this.state.themes._allThemes).forEach((theme) => {
198
+ defaultThemes.push({ label: theme.name, value: theme.name });
204
199
  });
205
200
  defaultThemes = defaultThemes.sort((a, b) => a.label.toUpperCase().localeCompare(b.label.toUpperCase()));
206
201
  const customThemes = this.context.customThemesManager.customThemes.map((ct) => {
@@ -1,12 +1,12 @@
1
- import { GMFBackgroundLayer } from '../gmf.js';
2
1
  import BaseLayer from '../layers/baselayer.js';
2
+ import { BasemapDefinition, LayerId } from '../layerdefinitions.js';
3
3
  declare class Basemap {
4
- id: number;
4
+ id: LayerId;
5
5
  name: string;
6
6
  thumbnail: string;
7
7
  opacity: number;
8
8
  layersList: BaseLayer[];
9
- constructor(elem: GMFBackgroundLayer, opacity?: number);
9
+ constructor(elem: BasemapDefinition, opacity?: number);
10
10
  get projection(): string | undefined;
11
11
  get opacityDisabled(): boolean;
12
12
  }
@@ -1,6 +1,7 @@
1
1
  import ThemeLayer from './layers/themelayer.js';
2
+ import { LayerId } from './layerdefinitions.js';
2
3
  export default class CustomTheme {
3
- id: number;
4
+ id: LayerId;
4
5
  layers: ThemeLayer[];
5
6
  name: string;
6
7
  icon: string;
@@ -0,0 +1,10 @@
1
+ import { GMFTreeItem, GMFTheme } from './gmf.js';
2
+ /**
3
+ * These are the interfaces for a Georama backend that differ from GMF
4
+ */
5
+ export interface GeoramaTreeItem extends Omit<GMFTreeItem, 'id'> {
6
+ id: string;
7
+ }
8
+ export interface GeoramaTheme extends Omit<GMFTheme, 'id'> {
9
+ id: string;
10
+ }
@@ -0,0 +1 @@
1
+ export {};
package/models/gmf.d.ts CHANGED
@@ -75,7 +75,6 @@ export interface GMFTheme {
75
75
  export interface GMFGroup extends GMFTreeItem {
76
76
  children: GMFTreeItem[];
77
77
  }
78
- export type GMFBackgroundLayer = GMFTreeItem;
79
78
  export type GMFServerOgcType = 'mapserver' | 'qgisserver' | 'georama' | 'geoserver' | 'arcgis' | 'other';
80
79
  export interface GMFServerOgc {
81
80
  url: string;
@@ -0,0 +1,13 @@
1
+ import { GMFTheme, GMFTreeItem } from './gmf.js';
2
+ import { GeoramaTheme, GeoramaTreeItem } from './georama.js';
3
+ /**
4
+ * Unified interfaces for layer definitions from different backends.
5
+ */
6
+ export type LayerId = number | string;
7
+ export interface TreeItemDefinition extends Omit<GMFTreeItem, 'id'>, Omit<GeoramaTreeItem, 'id'> {
8
+ id: LayerId;
9
+ }
10
+ export interface ThemeDefinition extends Omit<GMFTheme, 'id'>, Omit<GeoramaTheme, 'id'> {
11
+ id: LayerId;
12
+ }
13
+ export type BasemapDefinition = TreeItemDefinition;
@@ -0,0 +1 @@
1
+ export {};
@@ -1,12 +1,13 @@
1
1
  import GroupLayer from './grouplayer.js';
2
2
  import ThemeLayer from './themelayer.js';
3
+ import { LayerId } from '../layerdefinitions.js';
3
4
  type BaseLayerOptions = {
4
5
  isDefaultChecked?: boolean;
5
6
  disclaimer?: string;
6
7
  metadataUrl?: string;
7
8
  };
8
9
  declare abstract class BaseLayer {
9
- id: number;
10
+ id: LayerId;
10
11
  treeItemId: string;
11
12
  name: string;
12
13
  order: number;
@@ -27,6 +28,6 @@ declare abstract class BaseLayer {
27
28
  abstract get inactive(): boolean;
28
29
  abstract clone(): BaseLayer;
29
30
  parent?: ThemeLayer | GroupLayer;
30
- constructor(id: number, name: string, order: number, options?: BaseLayerOptions);
31
+ constructor(id: LayerId, name: string, order: number, options?: BaseLayerOptions);
31
32
  }
32
33
  export default BaseLayer;
@@ -1,4 +1,5 @@
1
1
  import BaseLayer from './baselayer.js';
2
+ import { LayerId } from '../layerdefinitions.js';
2
3
  import ILayerWithTime from './ilayerwithtime.js';
3
4
  import ITimeOptions from '../../tools/time/itimeoptions.js';
4
5
  export type GroupLayerOptions = {
@@ -21,7 +22,7 @@ declare class GroupLayer extends BaseLayer implements ILayerWithTime {
21
22
  timeAttribute?: string;
22
23
  timeRestriction?: string;
23
24
  children: BaseLayer[];
24
- constructor(id: number, name: string, order: number, options?: GroupLayerOptions);
25
+ constructor(id: LayerId, name: string, order: number, options?: GroupLayerOptions);
25
26
  clone(): GroupLayer;
26
27
  get active(): boolean;
27
28
  get inactive(): boolean;
@@ -1,6 +1,7 @@
1
1
  import BaseLayer from './baselayer.js';
2
2
  import GroupLayer from './grouplayer.js';
3
3
  import ThemeLayer from './themelayer.js';
4
+ import { LayerId } from '../layerdefinitions.js';
4
5
  type LayerOptions = {
5
6
  isDefaultChecked?: boolean;
6
7
  disclaimer?: string;
@@ -15,7 +16,7 @@ declare abstract class Layer extends BaseLayer {
15
16
  swiped: 'left' | 'right' | 'no';
16
17
  isLegendExpanded: boolean;
17
18
  parent: ThemeLayer | GroupLayer;
18
- constructor(id: number, name: string, order: number, options?: LayerOptions);
19
+ constructor(id: LayerId, name: string, order: number, options?: LayerOptions);
19
20
  get isTransparent(): boolean;
20
21
  get hasValidOpacity(): boolean;
21
22
  get active(): boolean;
@@ -1,4 +1,4 @@
1
- import { GMFTreeItem } from '../gmf.js';
1
+ import { LayerId, TreeItemDefinition } from '../layerdefinitions.js';
2
2
  import Layer from './layer.js';
3
3
  type LayerCogTilesOptions = {
4
4
  isDefaultChecked?: boolean;
@@ -9,9 +9,9 @@ type LayerCogTilesOptions = {
9
9
  };
10
10
  declare class LayerCog extends Layer {
11
11
  source: string;
12
- constructor(id: number, name: string, order: number, source: string, options?: GMFTreeItem | LayerCogTilesOptions);
12
+ constructor(id: LayerId, name: string, order: number, source: string, options?: TreeItemDefinition | LayerCogTilesOptions);
13
13
  clone(): LayerCog;
14
- private static isGMFTreeItem;
15
- private static getOptionsFromGMFTreeItem;
14
+ private static isTreeItemDefinition;
15
+ private static getOptionsFromDefinition;
16
16
  }
17
17
  export default LayerCog;
@@ -10,7 +10,7 @@ class LayerCog extends Layer {
10
10
  source;
11
11
  constructor(id, name, order, source, options) {
12
12
  let opts = options ?? {};
13
- opts = LayerCog.isGMFTreeItem(opts) ? LayerCog.getOptionsFromGMFTreeItem(opts) : opts;
13
+ opts = LayerCog.isTreeItemDefinition(opts) ? LayerCog.getOptionsFromDefinition(opts) : opts;
14
14
  super(id, name, order, opts);
15
15
  this.source = source;
16
16
  }
@@ -26,10 +26,10 @@ class LayerCog extends Layer {
26
26
  clonedObject.activeState = this.activeState;
27
27
  return clonedObject;
28
28
  }
29
- static isGMFTreeItem(options) {
29
+ static isTreeItemDefinition(options) {
30
30
  return 'id' in options;
31
31
  }
32
- static getOptionsFromGMFTreeItem(options) {
32
+ static getOptionsFromDefinition(options) {
33
33
  return {
34
34
  isDefaultChecked: options.metadata?.isChecked,
35
35
  metadataUrl: options.metadata?.metadataUrl,
@@ -3,6 +3,7 @@ import Layer from './layer.js';
3
3
  import Geometry from 'ol/geom/Geometry.js';
4
4
  import { Extent } from 'ol/extent.js';
5
5
  import ILayerWithLegend from './ilayerwithlegend.js';
6
+ import { LayerId } from '../layerdefinitions.js';
6
7
  declare class LayerLocalFile extends Layer implements ILayerWithLegend {
7
8
  _features: Feature<Geometry>[];
8
9
  lastModifiedDate: string;
@@ -12,7 +13,7 @@ declare class LayerLocalFile extends Layer implements ILayerWithLegend {
12
13
  extent: Extent;
13
14
  private readonly file;
14
15
  private readonly locale;
15
- constructor(id: number, order: number, file: File, features: Feature<Geometry>[], extent: Extent, locale: string, options?: {
16
+ constructor(id: LayerId, order: number, file: File, features: Feature<Geometry>[], extent: Extent, locale: string, options?: {
16
17
  isDefaultChecked?: boolean;
17
18
  });
18
19
  clone(): LayerLocalFile;
@@ -1,4 +1,5 @@
1
1
  import Layer from './layer.js';
2
+ import { LayerId } from '../layerdefinitions.js';
2
3
  type LayerVectorTilesOptions = {
3
4
  projection?: string;
4
5
  layerName?: string;
@@ -12,7 +13,7 @@ declare class LayerVectorTiles extends Layer {
12
13
  style: string;
13
14
  layerName?: string;
14
15
  projection?: string;
15
- constructor(id: number, name: string, order: number, style: string, options?: LayerVectorTilesOptions);
16
+ constructor(id: LayerId, name: string, order: number, style: string, options?: LayerVectorTilesOptions);
16
17
  clone(): LayerVectorTiles;
17
18
  }
18
19
  export default LayerVectorTiles;
@@ -1,5 +1,5 @@
1
1
  import WfsFilter from '../../tools/wfs/wfsfilter.js';
2
- import { GMFTreeItem } from '../gmf.js';
2
+ import { LayerId, TreeItemDefinition } from '../layerdefinitions.js';
3
3
  import ServerOgc from '../serverogc.js';
4
4
  import ILayerWithFilter from './ilayerwithfilter.js';
5
5
  import ILayerWithLegend from './ilayerwithlegend.js';
@@ -70,7 +70,7 @@ declare class LayerWms extends Layer implements ILayerWithLegend, ILayerWithFilt
70
70
  timeAttribute?: string;
71
71
  enumeratedAttributes?: string[];
72
72
  editable?: string;
73
- constructor(id: number, name: string, order: number, ogcServer: ServerOgc, options?: GMFTreeItem | LayerWmsOptions);
73
+ constructor(id: LayerId, name: string, order: number, ogcServer: ServerOgc, options?: TreeItemDefinition | LayerWmsOptions);
74
74
  clone(): LayerWms;
75
75
  static isResolutionRangeRestricted(minResolution: number | undefined, maxResolution: number | undefined): boolean | 0 | undefined;
76
76
  hasRestrictedResolution(): boolean | 0 | undefined;
@@ -82,8 +82,8 @@ declare class LayerWms extends Layer implements ILayerWithLegend, ILayerWithFilt
82
82
  get serverUniqueQueryId(): string;
83
83
  get wfsQueryable(): boolean;
84
84
  get wmsQueryableOnly(): boolean;
85
- private static isGMFTreeItem;
86
- private static getOptionsFromGMFTreeItem;
85
+ private static isTreeItemDefinition;
86
+ private static getOptionsFromDefinition;
87
87
  private static getDefaultWmsInfoFormat;
88
88
  }
89
89
  export default LayerWms;
@@ -44,7 +44,7 @@ class LayerWms extends Layer {
44
44
  editable;
45
45
  constructor(id, name, order, ogcServer, options) {
46
46
  let opts = options ?? {};
47
- opts = LayerWms.isGMFTreeItem(opts) ? LayerWms.getOptionsFromGMFTreeItem(opts) : opts;
47
+ opts = LayerWms.isTreeItemDefinition(opts) ? LayerWms.getOptionsFromDefinition(opts) : opts;
48
48
  super(id, name, order, opts);
49
49
  this.ogcServer = ogcServer;
50
50
  this.minResolution = opts?.minResolution;
@@ -152,10 +152,10 @@ class LayerWms extends Layer {
152
152
  get wmsQueryableOnly() {
153
153
  return this.queryable && !this.ogcServer.wfsSupport;
154
154
  }
155
- static isGMFTreeItem(options) {
155
+ static isTreeItemDefinition(options) {
156
156
  return 'id' in options;
157
157
  }
158
- static getOptionsFromGMFTreeItem(options) {
158
+ static getOptionsFromDefinition(options) {
159
159
  const opts = {
160
160
  isDefaultChecked: options.metadata?.isChecked,
161
161
  metadataUrl: options.metadata?.metadataUrl,
@@ -1,4 +1,4 @@
1
- import { GMFTreeItem } from '../gmf.js';
1
+ import { LayerId, TreeItemDefinition } from '../layerdefinitions.js';
2
2
  import ServerOgc from '../serverogc.js';
3
3
  import ILayerWithLegend from './ilayerwithlegend.js';
4
4
  import Layer from './layer.js';
@@ -48,12 +48,12 @@ declare class LayerWmts extends Layer implements ILayerWithLegend {
48
48
  hiDPILegendImages?: Record<string, string>;
49
49
  /** Linked ol layer, starting with an underscore to not be part of the proxy. **/
50
50
  _olayer?: TileLayer<WMTS>;
51
- constructor(id: number, name: string, order: number, url: string, layer: string, options?: GMFTreeItem | LayerWmtsOptions, ogcServer?: ServerOgc);
51
+ constructor(id: LayerId, name: string, order: number, url: string, layer: string, options?: TreeItemDefinition | LayerWmtsOptions, ogcServer?: ServerOgc);
52
52
  clone(): LayerWmts;
53
53
  get layerUniqueId(): string;
54
54
  hasRestrictedResolution(): boolean | 0 | undefined;
55
55
  isVisibleAtResolution(resolution: number): boolean;
56
- private static isGMFTreeItem;
57
- private static getOptionsFromGMFTreeItem;
56
+ private static isTreeItemDefinition;
57
+ private static getOptionsFromDefinition;
58
58
  }
59
59
  export default LayerWmts;
@@ -39,7 +39,7 @@ class LayerWmts extends Layer {
39
39
  _olayer;
40
40
  constructor(id, name, order, url, layer, options, ogcServer) {
41
41
  let opts = options ?? {};
42
- opts = LayerWmts.isGMFTreeItem(opts) ? LayerWmts.getOptionsFromGMFTreeItem(opts) : opts;
42
+ opts = LayerWmts.isTreeItemDefinition(opts) ? LayerWmts.getOptionsFromDefinition(opts) : opts;
43
43
  super(id, name, order, opts);
44
44
  this.url = url;
45
45
  this.layer = layer;
@@ -102,10 +102,10 @@ class LayerWmts extends Layer {
102
102
  }
103
103
  return resolution >= (this.minResolution ?? -1) && resolution <= (this.maxResolution ?? Infinity);
104
104
  }
105
- static isGMFTreeItem(options) {
105
+ static isTreeItemDefinition(options) {
106
106
  return 'id' in options;
107
107
  }
108
- static getOptionsFromGMFTreeItem(options) {
108
+ static getOptionsFromDefinition(options) {
109
109
  return {
110
110
  isDefaultChecked: options.metadata?.isChecked,
111
111
  metadataUrl: options.metadata?.metadataUrl,
@@ -1,4 +1,4 @@
1
- import { GMFTreeItem } from '../gmf.js';
1
+ import { LayerId, TreeItemDefinition } from '../layerdefinitions.js';
2
2
  import Layer from './layer.js';
3
3
  type LayerXYZTilesOptions = {
4
4
  isDefaultChecked?: boolean;
@@ -9,9 +9,9 @@ type LayerXYZTilesOptions = {
9
9
  };
10
10
  declare class LayerXYZ extends Layer {
11
11
  source: string;
12
- constructor(id: number, name: string, order: number, source: string, options?: GMFTreeItem | LayerXYZTilesOptions);
12
+ constructor(id: LayerId, name: string, order: number, source: string, options?: TreeItemDefinition | LayerXYZTilesOptions);
13
13
  clone(): LayerXYZ;
14
- private static isGMFTreeItem;
15
- private static getOptionsFromGMFTreeItem;
14
+ private static isTreeItemDefinition;
15
+ private static getOptionsFromDefinition;
16
16
  }
17
17
  export default LayerXYZ;
@@ -10,7 +10,7 @@ class LayerXYZ extends Layer {
10
10
  source;
11
11
  constructor(id, name, order, source, options) {
12
12
  let opts = options ?? {};
13
- opts = LayerXYZ.isGMFTreeItem(opts) ? LayerXYZ.getOptionsFromGMFTreeItem(opts) : opts;
13
+ opts = LayerXYZ.isTreeItemDefinition(opts) ? LayerXYZ.getOptionsFromDefinition(opts) : opts;
14
14
  super(id, name, order, opts);
15
15
  this.source = source;
16
16
  }
@@ -26,10 +26,10 @@ class LayerXYZ extends Layer {
26
26
  clonedObject.activeState = this.activeState;
27
27
  return clonedObject;
28
28
  }
29
- static isGMFTreeItem(options) {
29
+ static isTreeItemDefinition(options) {
30
30
  return 'id' in options;
31
31
  }
32
- static getOptionsFromGMFTreeItem(options) {
32
+ static getOptionsFromDefinition(options) {
33
33
  return {
34
34
  isDefaultChecked: options.metadata?.isChecked,
35
35
  metadataUrl: options.metadata?.metadataUrl,
@@ -1,5 +1,6 @@
1
1
  import BaseLayer from './baselayer.js';
2
2
  import { Coordinate } from 'ol/coordinate.js';
3
+ import { LayerId } from '../layerdefinitions.js';
3
4
  export type ThemeLayerOptions = {
4
5
  isDefaultChecked?: boolean;
5
6
  disclaimer?: string;
@@ -15,7 +16,7 @@ declare class ThemeLayer extends BaseLayer {
15
16
  location?: Coordinate;
16
17
  zoom?: number;
17
18
  children: BaseLayer[];
18
- constructor(id: number, name: string, order: number, icon?: string, options?: ThemeLayerOptions);
19
+ constructor(id: LayerId, name: string, order: number, icon?: string, options?: ThemeLayerOptions);
19
20
  clone(): ThemeLayer;
20
21
  get active(): boolean;
21
22
  get inactive(): boolean;
package/models/main.d.ts CHANGED
@@ -5,7 +5,9 @@ export { default as BasemapSwisstopoVectorTiles } from './basemaps/basemapswisst
5
5
  export { default as CustomTheme } from './customtheme.js';
6
6
  export type { FilterOperator, AttributeTypeGroup, LogicalFilterOperator } from './filter.js';
7
7
  export { filterOperatorValues, filterOperators, isFilterOperator, isValuelessFilterOperator, isTwoSidedFilterOperator, isListFilterOperator, operatorsByAttributeTypeGroup, logicalFilterOperators, isLogicalFilterOperator } from './filter.js';
8
- export type { GMFMetadata, GMFChildLayer, GMFTreeItem, GMFThemeFunctionality, GMFThemeFunctionalities, GMFTheme, GMFGroup, GMFBackgroundLayer, GMFServerOgcType, GMFServerOgc, GMFServerOgcAttributes, GMFServerOgcObjectAttributes, GMFServerOgcColumnAttributes } from './gmf.js';
8
+ export type { GeoramaTreeItem, GeoramaTheme } from './georama.js';
9
+ export type { GMFMetadata, GMFChildLayer, GMFTreeItem, GMFThemeFunctionality, GMFThemeFunctionalities, GMFTheme, GMFGroup, GMFServerOgcType, GMFServerOgc, GMFServerOgcAttributes, GMFServerOgcObjectAttributes, GMFServerOgcColumnAttributes } from './gmf.js';
10
+ export type { LayerId, TreeItemDefinition, ThemeDefinition, BasemapDefinition } from './layerdefinitions.js';
9
11
  export { default as BaseLayer } from './layers/baselayer.js';
10
12
  export type { GroupLayerOptions } from './layers/grouplayer.js';
11
13
  export { default as GroupLayer } from './layers/grouplayer.js';
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.2847697588",
8
+ "version": "1.2.0-dev.2854703696",
9
9
  "type": "module",
10
10
  "engines": {
11
11
  "node": ">=20.19.0"
package/styles/iframe.css CHANGED
@@ -10,14 +10,15 @@ body {
10
10
  overflow: hidden;
11
11
  }
12
12
 
13
- .logo {
13
+ girafe-header {
14
+ height: 2.25rem;
14
15
  position: absolute;
15
- top: 0.2rem;
16
- left: 1rem;
16
+ top: 0.5rem;
17
+ left: 0.5rem;
17
18
  z-index: 10;
18
- width: 8rem;
19
- background-color: #fff;
19
+ background-color: var(--bkg-color);
20
20
  opacity: 0.5;
21
+ border-radius: 4px;
21
22
  }
22
23
 
23
24
  #content {
package/styles/index.css CHANGED
@@ -39,10 +39,8 @@ girafe-theme-select {
39
39
  border-right: var(--app-standard-border);
40
40
  }
41
41
 
42
- .logo {
43
- margin: 0.2rem 1rem;
44
- height: var(--app-standard-height);
45
- opacity: 0.8;
42
+ girafe-header {
43
+ height: 100%;
46
44
  }
47
45
 
48
46
  girafe-search {
@@ -4,7 +4,7 @@
4
4
  <head>
5
5
  <meta charset="UTF-8" />
6
6
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7
- <meta name="description" content="GeoGirafe is an flexible application to build online geoportals." />
7
+ <meta name="description" content="GeoGirafe is a flexible application to build online geoportals." />
8
8
 
9
9
  <meta name="configs" content="main" />
10
10
  <link rel="config-main-url" href="config.json" />
@@ -107,7 +107,7 @@
107
107
  <span lang="en">Loading...</span>
108
108
  </div>
109
109
  <girafe-app>
110
- <img class="logo" alt="geogirafe-logo" src="images/logo/horizontal_black.svg" />
110
+ <girafe-header></girafe-header>
111
111
 
112
112
  <div id="content">
113
113
  <div id="main">
@@ -4,7 +4,7 @@
4
4
  <head>
5
5
  <meta charset="UTF-8" />
6
6
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7
- <meta name="description" content="GeoGirafe is an flexible application to build online geoportals." />
7
+ <meta name="description" content="GeoGirafe is a flexible application to build online geoportals." />
8
8
 
9
9
  <!--
10
10
  If redirect-url meta tag is present, a redirect will happen when the detected device is an iPhone or Android.
@@ -114,7 +114,7 @@
114
114
  <girafe-app>
115
115
  <header>
116
116
  <girafe-theme-select data-tour="theme-selection"></girafe-theme-select>
117
- <img class="logo" alt="geogirafe-logo" src="images/logo/horizontal_black.svg" />
117
+ <girafe-header></girafe-header>
118
118
  <div class="header-center-align">
119
119
  <girafe-search id="search" plugin="config:search">
120
120
  <!-- <div slot="search-tooltip">
@@ -3,7 +3,7 @@
3
3
  <html lang="en">
4
4
  <head>
5
5
  <meta charset="UTF-8" />
6
- <meta name="description" content="GeoGirafe is an flexible application to build online geoportals." />
6
+ <meta name="description" content="GeoGirafe is a flexible application to build online geoportals." />
7
7
  <meta
8
8
  name="viewport"
9
9
  content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover" />
@@ -80,6 +80,7 @@
80
80
  <span lang="en">Loading...</span>
81
81
  </div>
82
82
  <girafe-app>
83
+ <girafe-header></girafe-header>
83
84
  <div class="bottom-button-container-mobile">
84
85
  <girafe-align-north-button-mobile></girafe-align-north-button-mobile>
85
86
 
@@ -1 +1 @@
1
- {"version":"1.2.0-dev.2847697588", "build":"2847697588", "date":"14/09/2026"}
1
+ {"version":"1.2.0-dev.2854703696", "build":"2854703696", "date":"16/09/2026"}
@@ -29,6 +29,7 @@ import MobileSelectedLayerElementComponent from '../../components/themes-mobile/
29
29
  import GetDirectionsArtifact from '../../components/getdirections/component.js';
30
30
  import MenuMobileShareButton from '../../components/menu-buttons-mobile/MenuMobileShareButton.js';
31
31
  import ShareMobileComponent from '../../components/share-mobile/component.js';
32
+ import HeaderComponent from '../../components/header/component.js';
32
33
  export default class GeoGirafeAppMobile extends GeoGirafeApp {
33
34
  initializeInterface() {
34
35
  if (window.cordova) {
@@ -54,6 +55,7 @@ export default class GeoGirafeAppMobile extends GeoGirafeApp {
54
55
  customElements.define('girafe-map', DeprecatedMapComponent);
55
56
  customElements.define('girafe-search', MobileSearchComponent);
56
57
  customElements.define('girafe-offline', OfflineComponent);
58
+ customElements.define('girafe-header', HeaderComponent);
57
59
  customElements.define('girafe-theme-select', MobileThemeComponent);
58
60
  customElements.define('girafe-display-selector-button-mobile', DisplaySelectorButtonMobile);
59
61
  customElements.define('girafe-display-menu-button-mobile', DisplayMenuButtonMobile);
@@ -58,6 +58,7 @@ import FeedbackButtonComponent from '../../components/feedbackbutton/component.j
58
58
  import AddBookmarkComponent from '../../components/addbookmark/component.js';
59
59
  import DrawingToolbarComponent from '../../components/drawing-toolbar/component.js';
60
60
  import SearchFilterComponent from '../../components/search-filter/component.js';
61
+ import HeaderComponent from '../../components/header/component.js';
61
62
  export default class GeoGirafeApp {
62
63
  readyPromise;
63
64
  resolveReady;
@@ -108,6 +109,7 @@ export default class GeoGirafeApp {
108
109
  defineCoreComponents() {
109
110
  customElements.define('girafe-about', AboutComponent);
110
111
  customElements.define('girafe-advanced-filter', AdvancedFilterComponent);
112
+ customElements.define('girafe-header', HeaderComponent);
111
113
  customElements.define('girafe-basemap', BasemapComponent);
112
114
  customElements.define('girafe-coordinate', CoordinateComponent);
113
115
  customElements.define('girafe-contact', ContactComponent);
@@ -289,6 +289,14 @@ declare class GirafeConfig {
289
289
  permalink?: {
290
290
  sanitizeConfig?: SanitizeConfig;
291
291
  };
292
+ branding: {
293
+ logo: string;
294
+ logoDarkMode: string;
295
+ appTitle: string;
296
+ showAppTitleInHeader: boolean;
297
+ description: string;
298
+ prototypeBanner: boolean;
299
+ };
292
300
  extendedConfig?: Record<string, object>;
293
301
  static readonly DEFAULT_LOCALE = "en-US";
294
302
  /**
@@ -332,5 +340,6 @@ declare class GirafeConfig {
332
340
  private initApiConfig;
333
341
  private initConfigFiltering;
334
342
  private initPermalinkConfig;
343
+ private initBrandingConfig;
335
344
  }
336
345
  export default GirafeConfig;
@@ -39,6 +39,7 @@ class GirafeConfig {
39
39
  feedback;
40
40
  filtering;
41
41
  permalink;
42
+ branding;
42
43
  // The extended configuration can be used by third-party components or extensions
43
44
  // to add custom attributes to the GirafeConfig.
44
45
  extendedConfig;
@@ -81,6 +82,7 @@ class GirafeConfig {
81
82
  this.api = this.initApiConfig(config);
82
83
  this.filtering = this.initConfigFiltering(config);
83
84
  this.permalink = this.initPermalinkConfig(config);
85
+ this.branding = this.initBrandingConfig(config);
84
86
  try {
85
87
  this.search = this.initConfigSearch(config);
86
88
  }
@@ -510,5 +512,17 @@ class GirafeConfig {
510
512
  sanitizeConfig: defaultPermalinkSanitizeConfig
511
513
  });
512
514
  }
515
+ initBrandingConfig(config) {
516
+ // Logos, title and description fall back to GeoGirafe defaults if not specified
517
+ return {
518
+ logo: config.branding?.logo ?? 'images/logo/horizontal_black.svg',
519
+ logoDarkMode: config.branding?.logoDarkMode ?? config.branding?.logo ?? 'images/logo/horizontal_white.svg',
520
+ appTitle: config.branding?.appTitle ?? 'GeoGirafe',
521
+ showAppTitleInHeader: config.branding?.showAppTitleInHeader ?? false,
522
+ description: config.branding?.description ?? 'GeoGirafe is a flexible application to build online geoportals.',
523
+ // The prototype banner is shown by default
524
+ prototypeBanner: config.branding?.prototypeBanner ?? true
525
+ };
526
+ }
513
527
  }
514
528
  export default GirafeConfig;
@@ -1,5 +1,6 @@
1
1
  import { FilterOperator, LogicalFilterOperator } from '../../../models/filter.js';
2
2
  import { GMFServerOgcType } from '../../../models/gmf.js';
3
+ import { LayerId } from '../../../models/layerdefinitions.js';
3
4
  export type SharedFilterCondition = {
4
5
  property: string;
5
6
  propertyType?: string;
@@ -14,7 +15,7 @@ export type SharedFilter = {
14
15
  };
15
16
  export type SharedLayer = SharedInternalTheme | SharedInternalGroup | SharedInternalLayer | SharedExternalTheme | SharedExternalLayer;
16
17
  export type SharedInternalTheme = {
17
- id: number;
18
+ id: LayerId;
18
19
  order: number;
19
20
  checked: number;
20
21
  isExpanded: number;
@@ -25,7 +26,7 @@ export type SharedInternalTheme = {
25
26
  * And should forcefully be added to the layer tree because the user just didn't know this layer when he has created this shared state
26
27
  * Or if it was explicitly removed from the user, and then we won't have to display it again
27
28
  */
28
- excludedChildrenIds: number[];
29
+ excludedChildrenIds: Array<LayerId>;
29
30
  /**
30
31
  * When configuring the share with "preferNames=true", the name of the layer will be used to generate the sahred state
31
32
  * instead of the ID. But in this case, we also have to share the type os the layer, because tha layer name can be
@@ -38,7 +39,7 @@ export type SharedInternalTheme = {
38
39
  excludedChildrenNames: string[];
39
40
  };
40
41
  export type SharedInternalGroup = {
41
- id: number;
42
+ id: LayerId;
42
43
  order: number;
43
44
  checked: number;
44
45
  isExpanded: number;
@@ -50,7 +51,7 @@ export type SharedInternalGroup = {
50
51
  excludedChildrenNames: string[];
51
52
  };
52
53
  export type SharedInternalLayer = {
53
- id: number;
54
+ id: LayerId;
54
55
  order: number;
55
56
  checked: number;
56
57
  isExpanded: number;
@@ -87,7 +88,7 @@ export type SharedExternalLayer = {
87
88
  swiped?: 'left' | 'right' | 'no';
88
89
  };
89
90
  export type SharedBasemap = {
90
- id: number;
91
+ id: LayerId;
91
92
  name: string;
92
93
  opacity: number;
93
94
  };
@@ -13,9 +13,10 @@ import ObjectSelection from './objectselection.js';
13
13
  import type Theme from '../../models/theme.js';
14
14
  import GraphicalInterface from './graphicalInterface.js';
15
15
  import { GMFThemeFunctionalities, GMFThemeFunctionality } from '../../models/gmf.js';
16
+ import { LayerId } from '../../models/layerdefinitions.js';
16
17
  export type ThemesConfig = {
17
- _allThemes: Record<number, ThemeLayer>;
18
- _allFunctionalities: Record<number, GMFThemeFunctionalities>;
18
+ _allThemes: Record<LayerId, ThemeLayer>;
19
+ _allFunctionalities: Record<LayerId, GMFThemeFunctionalities>;
19
20
  isLoaded: boolean;
20
21
  lastSelectedTheme: ThemeLayer | CustomTheme | null;
21
22
  };
@@ -101,7 +102,7 @@ export default class State {
101
102
  */
102
103
  themes: ThemesConfig;
103
104
  functionalities: Record<string, GMFThemeFunctionality>;
104
- basemaps: Record<number, Basemap>;
105
+ basemaps: Record<LayerId, Basemap>;
105
106
  ogcServers: Record<string, ServerOgc>;
106
107
  activeBasemaps: Basemap[];
107
108
  projection: string;
@@ -1,5 +1,6 @@
1
1
  import GirafeSingleton from '../../base/GirafeSingleton.js';
2
2
  import BaseLayer from '../../models/layers/baselayer.js';
3
+ import { LayerId } from '../../models/layerdefinitions.js';
3
4
  import GroupLayer from '../../models/layers/grouplayer.js';
4
5
  import Layer from '../../models/layers/layer.js';
5
6
  import ThemeLayer from '../../models/layers/themelayer.js';
@@ -10,8 +11,8 @@ export type LayerTreeChanges = {
10
11
  export default class ThemesHelper extends GirafeSingleton {
11
12
  initializeSingleton(): void;
12
13
  private get state();
13
- findBaseLayerById(layerId: number): BaseLayer;
14
- findBaseLayerRecursiveById(layers: BaseLayer[], layerId: number): BaseLayer | null;
14
+ findBaseLayerById(layerId: LayerId): BaseLayer;
15
+ findBaseLayerRecursiveById(layers: BaseLayer[], layerId: LayerId): BaseLayer | null;
15
16
  findThemeByName(themename: string): ThemeLayer | null;
16
17
  findGroupByName(groupname: string): GroupLayer | null;
17
18
  findLayerByName(layername: string): Layer | null;
@@ -246,7 +246,7 @@ class ThemesManager extends GirafeSingleton {
246
246
  theme.children.push(layer);
247
247
  }
248
248
  }
249
- themes[index] = theme;
249
+ themes[themeJson.id] = theme;
250
250
  }
251
251
  return { themes, themesFunctionalities };
252
252
  }