@geogirafe/lib-geoportal 1.2.0-dev.2813242211 → 1.2.0-dev.2829121016

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 (85) hide show
  1. package/api/apicontext.d.ts +4 -0
  2. package/api/apicontext.js +8 -0
  3. package/api/apigeogirafeapp.js +7 -5
  4. package/api/style.css +18 -1
  5. package/assets/i18n/de.json +1 -0
  6. package/assets/i18n/en.json +1 -0
  7. package/assets/i18n/fr.json +1 -0
  8. package/assets/i18n/it.json +1 -0
  9. package/assets/icons/theme-checked.svg +1 -0
  10. package/components/drawing/component.d.ts +9 -2
  11. package/components/drawing/component.js +27 -10
  12. package/components/drawing-toolbar/component.js +2 -2
  13. package/components/extlayers/component.d.ts +0 -1
  14. package/components/extlayers/component.js +1 -6
  15. package/components/main.d.ts +3 -1
  16. package/components/main.js +3 -1
  17. package/components/{map → map-2d}/component.d.ts +9 -24
  18. package/components/{map → map-2d}/component.js +28 -260
  19. package/components/map-2d/deprecatedmapcomponent.d.ts +15 -0
  20. package/components/map-2d/deprecatedmapcomponent.js +24 -0
  21. package/components/{map → map-2d}/style.css +0 -30
  22. package/components/{map → map-2d}/tools/swipemanager.d.ts +1 -1
  23. package/components/map-3d/component.d.ts +50 -0
  24. package/components/map-3d/component.js +139 -0
  25. package/components/map-3d/style.css +34 -0
  26. package/components/map-3d/tools/globe.d.ts +42 -0
  27. package/components/map-3d/tools/globe.js +198 -0
  28. package/components/search/component.d.ts +5 -0
  29. package/components/search/component.js +71 -19
  30. package/components/search/style.css +36 -9
  31. package/components/selectiontool/component.d.ts +2 -2
  32. package/components/selectiontool/component.js +2 -2
  33. package/components/treeview/style.css +4 -0
  34. package/components/treeview/treeviewgroup/component.js +1 -1
  35. package/components/treeview/treeviewitem/component.js +1 -1
  36. package/components/treeview/treeviewtheme/component.d.ts +2 -0
  37. package/components/treeview/treeviewtheme/component.js +18 -3
  38. package/package.json +1 -1
  39. package/styles/iframe.css +18 -1
  40. package/styles/index.css +18 -1
  41. package/styles/mobile.css +18 -1
  42. package/templates/iframe.html +4 -1
  43. package/templates/index.html +4 -1
  44. package/templates/mobile.html +4 -1
  45. package/templates/public/about.json +1 -1
  46. package/templates/vite.config.js +43 -25
  47. package/tools/app/geogirafeapp-mobile.js +7 -2
  48. package/tools/app/geogirafeapp.js +7 -2
  49. package/tools/context/context.d.ts +4 -0
  50. package/tools/context/context.js +8 -0
  51. package/tools/context/icontext.d.ts +4 -0
  52. package/tools/drawing/cesiumDrawing.d.ts +9 -5
  53. package/tools/drawing/cesiumDrawing.js +8 -8
  54. package/tools/drawing/olDrawing.d.ts +2 -2
  55. package/tools/drawing/olDrawing.js +1 -1
  56. package/tools/main.d.ts +2 -0
  57. package/tools/main.js +2 -0
  58. package/tools/selection/selectionmanager.d.ts +19 -0
  59. package/tools/selection/selectionmanager.js +80 -0
  60. package/tools/state/componentManager.d.ts +1 -1
  61. package/tools/tests/mockcontext.d.ts +4 -0
  62. package/tools/tests/mockcontext.js +8 -0
  63. package/tools/themes/themeshelper.d.ts +44 -1
  64. package/tools/themes/themeshelper.js +149 -16
  65. package/{components/map/tools → tools/wmts}/wmtsmanager.d.ts +4 -7
  66. package/{components/map/tools → tools/wmts}/wmtsmanager.js +7 -10
  67. /package/components/{map → map-2d}/tools/cogmanager.d.ts +0 -0
  68. /package/components/{map → map-2d}/tools/cogmanager.js +0 -0
  69. /package/components/{map → map-2d}/tools/contextmenu.d.ts +0 -0
  70. /package/components/{map → map-2d}/tools/contextmenu.js +0 -0
  71. /package/components/{map → map-2d}/tools/drawingmanager.d.ts +0 -0
  72. /package/components/{map → map-2d}/tools/drawingmanager.js +0 -0
  73. /package/components/{map → map-2d}/tools/focusfeature.d.ts +0 -0
  74. /package/components/{map → map-2d}/tools/focusfeature.js +0 -0
  75. /package/components/{map → map-2d}/tools/osmmanager.d.ts +0 -0
  76. /package/components/{map → map-2d}/tools/osmmanager.js +0 -0
  77. /package/components/{map → map-2d}/tools/swipemanager.js +0 -0
  78. /package/components/{map → map-2d}/tools/vectortilesmanager.d.ts +0 -0
  79. /package/components/{map → map-2d}/tools/vectortilesmanager.js +0 -0
  80. /package/components/{map → map-2d}/tools/viewmanager.d.ts +0 -0
  81. /package/components/{map → map-2d}/tools/viewmanager.js +0 -0
  82. /package/components/{map → map-2d}/tools/xyzmanager.d.ts +0 -0
  83. /package/components/{map → map-2d}/tools/xyzmanager.js +0 -0
  84. /package/components/{map → map-3d}/tools/wmsmanager3d.d.ts +0 -0
  85. /package/components/{map → map-3d}/tools/wmsmanager3d.js +0 -0
@@ -0,0 +1,139 @@
1
+ import { html as uHtml } from 'uhtml';
2
+ // SPDX-License-Identifier: Apache-2.0
3
+ import GirafeHTMLElement from '../../base/GirafeHTMLElement.js';
4
+ import LayerWms from '../../models/layers/layerwms.js';
5
+ /**
6
+ * The 3D Map.
7
+ *
8
+ * The Component itself stays lightweight: Cesium and OLCesium are only imported when the 3D Map
9
+ * is displayed for the first time. Everything that needs those libraries lives in ./tools/globe.
10
+ * The synchronization with the 2D Map is done by OLCesium, through the OpenLayers map of the
11
+ * MapManager. There is therefore no direct dependency to the map-2d Component.
12
+ */
13
+ export default class Map3dComponent extends GirafeHTMLElement {
14
+ templateUrl = null;
15
+ styleUrls = null;
16
+ template = () => {
17
+ return uHtml `<style>
18
+ .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}}
19
+ </style><style>
20
+ #cs-map{background-color:var(--bkg-color);width:100%;height:100%;position:absolute;top:0;overflow:hidden}.hidden{display:none}.center{text-align:center;margin:10px;position:absolute;top:50%;left:50%;transform:translate(-50%,-50%)}.loading span{color:var(--text-color);margin-top:1rem;font-weight:600;display:block}.loading span.quote{font-style:italic;font-weight:300}
21
+ </style>
22
+ <style>${this.customStyle}</style>
23
+ <div id="cs-map"><div class="${this.loading ? 'loading center' : 'loading hidden'}"><img alt="loading-icon" src="icons/loading.svg" class="gg-spin"> <span i18n="Loading cesium...">Loading cesium...</span> <span class="quote" i18n="cesium-loading-quote">Please be patient, like a giraffe reaching for the tastiest leaves.</span></div></div>
24
+ ${this.htmlUnsafe(this.feedbackTemplateHtml ?? '')}`;
25
+ };
26
+ globe = null;
27
+ map3dTarget;
28
+ loading = false;
29
+ pixelTolerance = 10;
30
+ /** Promise of the currently running globe creation, to prevent creating it twice. */
31
+ globeCreation = null;
32
+ /** The OLCesium instance, once the globe has been created. */
33
+ get map3d() {
34
+ return this.globe?.olCesium;
35
+ }
36
+ get config() {
37
+ return this.context.configManager.Config;
38
+ }
39
+ constructor() {
40
+ super('map-3d');
41
+ }
42
+ subscribe(path, callback) {
43
+ // @ts-expect-error The call would have succeeded against this implementation,
44
+ // but implementation signatures of overloads are not externally visible.
45
+ return super.subscribe(path, callback);
46
+ }
47
+ /**
48
+ * The Map needs to be displayed in Fullscreen Mode.
49
+ */
50
+ get isFullscreenComponent() {
51
+ return true;
52
+ }
53
+ connectedCallback() {
54
+ super.connectedCallback();
55
+ this.render();
56
+ this.registerEvents();
57
+ this.onGlobeToggled();
58
+ }
59
+ render() {
60
+ super.render();
61
+ this.map3dTarget = this.shadow.getElementById('cs-map');
62
+ }
63
+ registerEvents() {
64
+ this.subscribe('globe.display', async () => {
65
+ await this.onGlobeToggled();
66
+ this.globe?.setCamera(this.state.globe.camera);
67
+ });
68
+ this.subscribe('globe.shadows', (_oldShadows, newShadows) => this.globe?.setShadows(newShadows));
69
+ this.subscribe('globe.camera', async (_oldCamera, newCamera) => {
70
+ await this.onGlobeToggled();
71
+ this.globe?.setCamera(newCamera);
72
+ });
73
+ this.subscribe('activeBasemaps', (_, newBasemaps) => this.globe?.onChangeBasemaps(newBasemaps));
74
+ this.subscribe(/activeBasemaps\.\d\.opacity/, (_oldOpacity, _newOpacity, basemap) => this.globe?.changeBasemapOpacity(basemap));
75
+ this.subscribe(/layers\.layersList\..*\.activeState/, (_oldActive, _newActive, layer) => {
76
+ if (layer instanceof LayerWms) {
77
+ this.globe?.onLayerToggled(layer);
78
+ }
79
+ });
80
+ this.subscribe(/layers\.layersList\..*\.opacity/, (_oldOpacity, _newOpacity, layer) => this.globe?.changeOpacity(layer));
81
+ this.subscribe(/layers\.layersList\..*\.filter/, (_oldFilter, _newFilter, layer) => this.globe?.changeFilter(layer));
82
+ this.subscribe('application.isReady', (_, isLoaded) => {
83
+ if (isLoaded) {
84
+ // The globe may not be ready when the rest of the application is initialized.
85
+ // The camera of a shared state can only be applied once everything is loaded.
86
+ this.globe?.setCamera(this.state.globe.camera);
87
+ }
88
+ });
89
+ }
90
+ /**
91
+ * Lazily loads Cesium/OLCesium and creates the globe. Does nothing if no 3D configuration
92
+ * is available or if the globe has already been created.
93
+ */
94
+ async createGlobe() {
95
+ if (this.globe || !this.config.map3d) {
96
+ return;
97
+ }
98
+ this.globeCreation ??= (async () => {
99
+ this.loading = true;
100
+ this.refreshRender();
101
+ const { default: Globe } = await import('./tools/globe');
102
+ const globe = new Globe(this.context, this.name, this.pixelTolerance);
103
+ await globe.initialize(this.map3dTarget);
104
+ this.globe = globe;
105
+ this.loading = false;
106
+ this.refreshRender();
107
+ this.state.globe.loaded = true;
108
+ })();
109
+ return this.globeCreation;
110
+ }
111
+ /**
112
+ * Shows or hides the 3D Map and sizes it depending on the current display mode.
113
+ * The 2D Map takes care of its own sizing.
114
+ */
115
+ async onGlobeToggled() {
116
+ if (this.state.globe.display === '3D') {
117
+ // Full screen globe has been enabled
118
+ this.style.display = 'block';
119
+ this.style.left = '0';
120
+ this.style.width = '100%';
121
+ await this.createGlobe();
122
+ this.globe?.setEnabled(true);
123
+ }
124
+ else if (this.state.globe.display === '2D/3D') {
125
+ // Side by side has been enabled
126
+ this.style.display = 'block';
127
+ this.style.left = '45%';
128
+ this.style.width = '55%';
129
+ await this.createGlobe();
130
+ this.globe?.setEnabled(true);
131
+ }
132
+ else {
133
+ // 3d map is not visible
134
+ this.globe?.setEnabled(false);
135
+ this.style.display = 'none';
136
+ this.unregisterInteractionListeners('globe.select');
137
+ }
138
+ }
139
+ }
@@ -0,0 +1,34 @@
1
+ /* SPDX-License-Identifier: Apache-2.0 */
2
+ #cs-map {
3
+ background-color: var(--bkg-color);
4
+ position: absolute;
5
+ top: 0;
6
+ height: 100%;
7
+ width: 100%;
8
+ overflow: hidden;
9
+ }
10
+
11
+ .hidden {
12
+ display: none;
13
+ }
14
+
15
+ .center {
16
+ position: absolute;
17
+ top: 50%;
18
+ left: 50%;
19
+ transform: translate(-50%, -50%);
20
+ margin: 10px;
21
+ text-align: center;
22
+ }
23
+
24
+ .loading span {
25
+ color: var(--text-color);
26
+ display: block;
27
+ margin-top: 1rem;
28
+ font-weight: 600;
29
+ }
30
+
31
+ .loading span.quote {
32
+ font-style: italic;
33
+ font-weight: 300;
34
+ }
@@ -0,0 +1,42 @@
1
+ import IGirafeContext from '../../../tools/context/icontext.js';
2
+ import BaseLayer from '../../../models/layers/baselayer.js';
3
+ import LayerWms from '../../../models/layers/layerwms.js';
4
+ import Basemap from '../../../models/basemaps/basemap.js';
5
+ import { CameraConfig } from '../../../tools/state/globe.js';
6
+ declare global {
7
+ interface Window {
8
+ Cesium: unknown;
9
+ }
10
+ }
11
+ /**
12
+ * The 3D Globe, based on Cesium and synchronized with the OpenLayers map by OLCesium.
13
+ *
14
+ * This module is the only place where Cesium and OLCesium are used. It is loaded on demand
15
+ * by the map-3d Component, so that those (heavy) libraries stay out of the initial bundle
16
+ * as long as the 3D Map is not displayed.
17
+ */
18
+ export default class Globe {
19
+ private readonly context;
20
+ private readonly toolName;
21
+ private readonly pixelTolerance;
22
+ olCesium: any;
23
+ private scene;
24
+ private wmsManager3d;
25
+ private get state();
26
+ private get config();
27
+ constructor(context: IGirafeContext, toolName: string, pixelTolerance: number);
28
+ /**
29
+ * Loads Cesium and OLCesium and initializes the globe on the given target element.
30
+ */
31
+ initialize(target: HTMLDivElement): Promise<void>;
32
+ private registerSelection;
33
+ setEnabled(enabled: boolean): void;
34
+ setShadows(shadows: boolean): void;
35
+ setCamera(camera: CameraConfig | null): void;
36
+ private addAllActiveLayers;
37
+ onLayerToggled(layer: LayerWms): void;
38
+ changeOpacity(layer: BaseLayer): void;
39
+ changeBasemapOpacity(basemap: Basemap): void;
40
+ changeFilter(layer: LayerWms): void;
41
+ onChangeBasemaps(basemaps: Basemap[]): void;
42
+ }
@@ -0,0 +1,198 @@
1
+ import * as Cesium from 'cesium';
2
+ import proj4 from 'proj4';
3
+ import WmsManager3d from './wmsmanager3d.js';
4
+ import GroupLayer from '../../../models/layers/grouplayer.js';
5
+ import LayerWms from '../../../models/layers/layerwms.js';
6
+ import { applyOpacityToLayers } from '../../../tools/utils/utils.js';
7
+ /**
8
+ * The 3D Globe, based on Cesium and synchronized with the OpenLayers map by OLCesium.
9
+ *
10
+ * This module is the only place where Cesium and OLCesium are used. It is loaded on demand
11
+ * by the map-3d Component, so that those (heavy) libraries stay out of the initial bundle
12
+ * as long as the 3D Map is not displayed.
13
+ */
14
+ export default class Globe {
15
+ context;
16
+ toolName;
17
+ pixelTolerance;
18
+ // TODO REG : Howto use the right type here without importing the whole library (it needs to be imported only on demand) ?
19
+ // This works but needs the library: type OLCesiumType = typeof OLCesium;
20
+ olCesium;
21
+ scene;
22
+ wmsManager3d;
23
+ get state() {
24
+ return this.context.stateManager.state;
25
+ }
26
+ get config() {
27
+ return this.context.configManager.Config.map3d;
28
+ }
29
+ constructor(context, toolName, pixelTolerance) {
30
+ this.context = context;
31
+ this.toolName = toolName;
32
+ this.pixelTolerance = pixelTolerance;
33
+ }
34
+ /**
35
+ * Loads Cesium and OLCesium and initializes the globe on the given target element.
36
+ */
37
+ async initialize(target) {
38
+ window.Cesium = Cesium;
39
+ const olcs = await import('olcs');
40
+ this.olCesium = new olcs.default({
41
+ map: this.context.mapManager.getMap(),
42
+ target: target,
43
+ time: () => {
44
+ const date = new Date(this.state.globe.shadowsTimestamp);
45
+ return Number.isNaN(date.getTime()) ? Cesium.JulianDate.now() : Cesium.JulianDate.fromDate(date);
46
+ }
47
+ });
48
+ const scene = this.olCesium.getCesiumScene();
49
+ this.scene = scene;
50
+ const config = this.config;
51
+ scene.screenSpaceCameraController.maximumZoomDistance = config.maximumZoomDistance ?? 30000;
52
+ // Add terrain
53
+ if (config.terrainUrl) {
54
+ scene.terrainProvider = await Cesium.CesiumTerrainProvider.fromUrl(config.terrainUrl);
55
+ }
56
+ // Add terrain imagery
57
+ let coverage = Cesium.Rectangle.MAX_VALUE;
58
+ if (config.terrainImagery) {
59
+ if (config.terrainImagery.coverageArea) {
60
+ coverage = Cesium.Rectangle.fromDegrees(...config.terrainImagery.coverageArea);
61
+ }
62
+ scene.imageryLayers.addImageryProvider(new Cesium.UrlTemplateImageryProvider({
63
+ url: config.terrainImagery.url,
64
+ minimumLevel: config.terrainImagery.minLoD ?? 0,
65
+ maximumLevel: config.terrainImagery.maxLoD,
66
+ tilingScheme: config.terrainImagery.srid === 3857
67
+ ? new Cesium.WebMercatorTilingScheme()
68
+ : new Cesium.GeographicTilingScheme(),
69
+ rectangle: coverage
70
+ }));
71
+ }
72
+ // Add 3D-Tiles layers
73
+ const tilesetOptions = {
74
+ // If the error of the model is higher than this, we increase the resolution
75
+ maximumScreenSpaceError: 0.5,
76
+ // Enable different level of details based on the distance from the camera
77
+ dynamicScreenSpaceError: true,
78
+ // Model error at the max distance from the camera (higher = distant models are of lower quality)
79
+ dynamicScreenSpaceErrorFactor: config.tilesetsMaxError ?? 7
80
+ };
81
+ config.tilesetsUrls.forEach((tilesetUrl) => {
82
+ Cesium.Cesium3DTileset.fromUrl(tilesetUrl, tilesetOptions).then((t) => scene.primitives.add(t));
83
+ });
84
+ // REG: ambientOcclusion was temporary deactivated because of performance impact and horizontal lines artefacts
85
+ // const ambientOcclusion = scene.postProcessStages.ambientOcclusion;
86
+ // ambientOcclusion.enabled = true;
87
+ // ambientOcclusion.uniforms.bias = 0.5;
88
+ // ambientOcclusion.uniforms.stepSize = 1;
89
+ // ambientOcclusion.uniforms.blurStepSize = 1;
90
+ // REG: Adding the following line solves the problem, but it remains a log less performatn with ambiant occlusion.
91
+ // So I don't know what we want to do with it.
92
+ // See https://github.com/CesiumGS/cesium/issues/13039#issuecomment-3583233494
93
+ // viewer.camera.frustum.near = 1.0;
94
+ this.registerSelection();
95
+ this.wmsManager3d = new WmsManager3d(scene, this.context);
96
+ this.state.layers.layersList.forEach((l) => this.addAllActiveLayers(l));
97
+ const camera = scene.camera;
98
+ camera.changed.addEventListener(() => {
99
+ console.debug('Cesium camera moved');
100
+ this.state.globe.camera = {
101
+ heading: camera.heading,
102
+ pitch: camera.pitch,
103
+ roll: camera.roll
104
+ };
105
+ }, 1);
106
+ }
107
+ registerSelection() {
108
+ const scene = this.scene;
109
+ const pickOnGlobe = (position) => {
110
+ const ray = scene.camera.getPickRay(position);
111
+ return ray == undefined ? undefined : scene.globe.pick(ray, scene);
112
+ };
113
+ const cesiumScreenToLocalCoord = (position) => {
114
+ const cart = Cesium.Cartographic.fromCartesian(pickOnGlobe(position));
115
+ const longLat = [Cesium.Math.toDegrees(cart.longitude), Cesium.Math.toDegrees(cart.latitude)];
116
+ return proj4('EPSG:4326', this.context.configManager.Config.map.srid, longLat);
117
+ };
118
+ this.context.userInteractionManager.registerListener('globe.select', true, this.toolName);
119
+ const eventHandler = new Cesium.ScreenSpaceEventHandler(scene.canvas);
120
+ eventHandler.setInputAction((event) => {
121
+ // If the click is on the map and selection is allowed
122
+ if (Cesium.defined(event.position) &&
123
+ this.context.userInteractionManager.canListenerExecute('globe.select', this.toolName)) {
124
+ const topLeftScreen = event.position.clone();
125
+ topLeftScreen.x -= this.pixelTolerance;
126
+ topLeftScreen.y -= this.pixelTolerance;
127
+ const bottomRightScreen = event.position.clone();
128
+ bottomRightScreen.x += this.pixelTolerance;
129
+ bottomRightScreen.y += this.pixelTolerance;
130
+ const topLeft = cesiumScreenToLocalCoord(topLeftScreen);
131
+ const bottomRight = cesiumScreenToLocalCoord(bottomRightScreen);
132
+ this.context.selectionManager.select([topLeft[0], topLeft[1], bottomRight[0], bottomRight[1]]);
133
+ }
134
+ }, Cesium.ScreenSpaceEventType.LEFT_CLICK);
135
+ }
136
+ setEnabled(enabled) {
137
+ this.olCesium.setEnabled(enabled);
138
+ }
139
+ setShadows(shadows) {
140
+ this.scene.shadowMap.enabled = this.scene.globe.enableLighting = shadows;
141
+ }
142
+ setCamera(camera) {
143
+ if (!camera) {
144
+ return;
145
+ }
146
+ this.scene.camera.setView({
147
+ destination: this.scene.camera.position, // Keep current position
148
+ orientation: {
149
+ heading: camera.heading,
150
+ pitch: camera.pitch,
151
+ roll: camera.roll
152
+ }
153
+ });
154
+ }
155
+ addAllActiveLayers(layer) {
156
+ if (layer instanceof LayerWms) {
157
+ if (layer.active) {
158
+ this.wmsManager3d.addLayer(layer);
159
+ }
160
+ }
161
+ else if (layer instanceof GroupLayer) {
162
+ layer.children.forEach((l) => this.addAllActiveLayers(l));
163
+ }
164
+ }
165
+ onLayerToggled(layer) {
166
+ if (layer.active) {
167
+ this.wmsManager3d.addLayer(layer);
168
+ }
169
+ else {
170
+ this.wmsManager3d.removeLayer(layer);
171
+ }
172
+ }
173
+ changeOpacity(layer) {
174
+ if (layer instanceof LayerWms) {
175
+ this.wmsManager3d.changeOpacity(layer);
176
+ }
177
+ }
178
+ changeBasemapOpacity(basemap) {
179
+ applyOpacityToLayers(basemap.opacity, basemap.layersList, (layer) => this.changeOpacity(layer));
180
+ }
181
+ changeFilter(layer) {
182
+ this.wmsManager3d.changeFilter(layer);
183
+ }
184
+ onChangeBasemaps(basemaps) {
185
+ this.wmsManager3d.removeAllBasemapLayers();
186
+ for (const layer of basemaps.flatMap((basemap) => basemap.layersList)) {
187
+ if (layer instanceof LayerWms) {
188
+ this.wmsManager3d.addBasemapLayer(layer);
189
+ }
190
+ }
191
+ // Apply default opacity
192
+ for (const basemap of basemaps) {
193
+ if (!basemap.opacityDisabled) {
194
+ this.changeBasemapOpacity(basemap);
195
+ }
196
+ }
197
+ }
198
+ }
@@ -44,10 +44,12 @@ declare class SearchComponent extends GirafeHTMLElement {
44
44
  * @param e
45
45
  */
46
46
  doSearchDebounced(e: Event): Promise<void>;
47
+ addFullThemeIfNotPresent(feature: Feature): void;
47
48
  getGroupIcon(searchGroup: string): any;
48
49
  getProviderClass(provider: string): "hidden" | "provider entry";
49
50
  getGroupClass(group: string): "hidden" | "group entry";
50
51
  getLabel(result: Feature): unknown;
52
+ isFeatureActionThemeRelated(feature: Feature): boolean;
51
53
  onMouseOver(result: Feature): void;
52
54
  onMouseLeave(): void;
53
55
  private focusResultFromIndex;
@@ -56,6 +58,9 @@ declare class SearchComponent extends GirafeHTMLElement {
56
58
  private addFeatureToPreview;
57
59
  clearPreview(): void;
58
60
  onSelect(feature: Feature): void;
61
+ private closeDropdown;
62
+ private updateSearchboxWithResult;
63
+ private getFeatureFirstAction;
59
64
  private addResultToTreeView;
60
65
  private zoomTo;
61
66
  onMouseMove(): void;
@@ -28,10 +28,10 @@ class SearchComponent extends GirafeHTMLElement {
28
28
  return uHtml `<style>
29
29
  .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}}
30
30
  </style><style>
31
- .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;& slot[name=search-tooltip]{display:none}}#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}.entry{width:34rem;color:var(--text-color);padding:.3rem 1rem;line-height:1.3rem;display:inline-block}.provider,.group{font-size:1.2rem;font-weight:600;line-height:3rem}.provider{background-color:color-mix(in srgb, var(--bkg-color-grad1) 75%, transparent);margin-top:1rem;font-size:1.5rem}#results>.provider:first-child{margin-top:0}.result{cursor:pointer;text-align:left;background:0 0;border:none;border-top:1px solid #ccc;width:100%}.entry img{width:.8em;margin-right:1rem}.entry span{text-transform:uppercase}.result.selected{color:var(--text-color);background-color:var(--bkg-color-grad2)!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}
31
+ .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;& slot[name=search-tooltip]{display:none}}#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,.theme-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}.entry{width:34rem;color:var(--text-color);padding:.3rem 1rem;line-height:1.3rem;display:inline-block}.entry img{width:.8em;margin-right:1rem}.entry span{text-transform:uppercase}.provider,.group{font-size:1.2rem;font-weight:600;line-height:3rem}.provider{background-color:color-mix(in srgb, var(--bkg-color-grad1) 75%, transparent);margin-top:1rem;font-size:1.5rem}#results>.provider:first-child{margin-top:0}.result{background:0 0;border-top:1px solid #ccc;align-items:stretch;width:100%;padding:0;display:flex}.result button{cursor:pointer;text-align:left;width:100%;color:var(--text-color);background:0 0;border:none;padding:.3rem 1rem}.result button:hover{color:var(--text-color);background-color:var(--bkg-color-grad2)!important}.result .tools{width:1.3rem;margin:0}.result .tools button{height:-webkit-fill-available;margin:0;padding:0 .3rem}.result .tools button img{margin:0;padding:0}#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}
32
32
  </style>
33
33
  <style>${this.customStyle}</style>
34
- <link rel="stylesheet" href="lib/vanilla-picker/vanilla-picker.csp.css"><link rel="stylesheet" href="styles/girafecolorpicker.css"><div id="searchbox"><slot name="search-tooltip"></slot><input id="search" class="gg-input" length="20" maxlength="1000" autocomplete="off" autocorrect="off" i18n="Search..." placeholder="Search..." aria-label="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="${!this.allResults.length || this.forceHide ? 'hidden' : ''}" onmousemove="${() => this.onMouseMove()}">${(Object.keys(this.groupedResults) ?? []).map(provider => uHtml `<div class="${this.getProviderClass(provider)}"><img alt="provider icon" src="${this.searchIcon}"> <span i18n="${provider}">${provider}</span></div>${(Object.keys(this.groupedResults[provider]) ?? []).map(group => uHtml `<div class="${this.getGroupClass(group)}"><img alt="result icon" src="${this.getGroupIcon(group)}"> <span i18n="${group}">${group}</span></div>${(this.groupedResults[provider][group] ?? []).map(result => uHtml ` <button class="${result.get('selected') ? 'result entry selected' : 'result entry'}" onmousedown="${() => this.onMouseDown()}" onclick="${() => this.onSelect(result)}" onmouseover="${() => this.onMouseOver(result)}" onmouseleave="${() => this.onMouseLeave()}">${this.getLabel(result)}</button> `)} `)} `)}</div><div id="no-result" class="${this.showNoResultWarning ? 'shown-then-fadeout' : ''}"><span i18n="${'No match found'}"></span></div></div>
34
+ <link rel="stylesheet" href="lib/vanilla-picker/vanilla-picker.csp.css"><link rel="stylesheet" href="styles/girafecolorpicker.css"><div id="searchbox"><slot name="search-tooltip"></slot><input id="search" class="gg-input" length="20" maxlength="1000" autocomplete="off" autocorrect="off" i18n="Search..." placeholder="Search..." aria-label="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="${!this.allResults.length || this.forceHide ? 'hidden' : ''}" onmousemove="${() => this.onMouseMove()}">${(Object.keys(this.groupedResults) ?? []).map(provider => uHtml `<div class="${this.getProviderClass(provider)}"><img alt="provider icon" src="${this.searchIcon}"> <span i18n="${provider}">${provider}</span></div>${(Object.keys(this.groupedResults[provider]) ?? []).map(group => uHtml `<div class="${this.getGroupClass(group)}"><img alt="result icon" src="${this.getGroupIcon(group)}"> <span i18n="${group}">${group}</span></div>${(this.groupedResults[provider][group] ?? []).map(result => uHtml `<div class="${result.get('selected') ? 'result entry selected' : 'result entry'}"><button onmouseover="${() => this.onMouseOver(result)}" onmouseleave="${() => this.onMouseLeave()}" onmousedown="${() => this.onMouseDown()}" onclick="${() => this.onSelect(result)}">${this.getLabel(result)}</button><div class="${this.isFeatureActionThemeRelated(result) ? 'tools' : 'hidden'}"><button class="addFullTree" onclick="${() => this.addFullThemeIfNotPresent(result)}"><img class="theme-icon" alt="theme-icon" src="icons/theme-checked.svg" tip="Complete the theme"></button></div></div>`)} `)} `)}</div><div id="no-result" class="${this.showNoResultWarning ? 'shown-then-fadeout' : ''}"><span i18n="${'No match found'}"></span></div></div>
35
35
  ${this.htmlUnsafe(this.feedbackTemplateHtml ?? '')}`;
36
36
  };
37
37
  searchIcon = SearchIcon;
@@ -205,6 +205,33 @@ ${this.htmlUnsafe(this.feedbackTemplateHtml ?? '')}`;
205
205
  this.doSearch(syntheticEvent);
206
206
  }, 300);
207
207
  }
208
+ addFullThemeIfNotPresent(feature) {
209
+ this.clearPreview();
210
+ const action = this.getFeatureFirstAction(feature);
211
+ if (!action) {
212
+ return;
213
+ }
214
+ const layer = this.context.themesHelper.findLayerByName(action.data);
215
+ if (!layer) {
216
+ return;
217
+ }
218
+ const theme = this.context.themesHelper.getOriginalTheme(layer);
219
+ if (!theme) {
220
+ return;
221
+ }
222
+ const added = this.context.themesHelper.addThemeIfNotPresent(theme);
223
+ if (!added) {
224
+ // If it's not added because it's already in the tree, complete the theme.
225
+ this.context.themesHelper.completeTheme(theme);
226
+ }
227
+ const insertedLayer = this.context.themesHelper.findBaseLayerRecursiveById(this.state.layers.layersList, layer.id);
228
+ if (insertedLayer) {
229
+ insertedLayer.activeState = 'on';
230
+ }
231
+ this.closeDropdown(feature);
232
+ this.onFocusOut();
233
+ this.updateSearchboxWithResult(feature);
234
+ }
208
235
  getGroupIcon(searchGroup) {
209
236
  switch (searchGroup) {
210
237
  case 'Group':
@@ -230,6 +257,16 @@ ${this.htmlUnsafe(this.feedbackTemplateHtml ?? '')}`;
230
257
  getLabel(result) {
231
258
  return result.get('label_html') ? this.htmlUnsafe(result.get('label_html')) : result.get('label');
232
259
  }
260
+ isFeatureActionThemeRelated(feature) {
261
+ const action = this.getFeatureFirstAction(feature);
262
+ const kind = action?.action;
263
+ const hasThemeRelatedAction = kind?.startsWith('add_layer') || kind?.startsWith('add_group') || kind?.startsWith('add_theme') || false;
264
+ if (!hasThemeRelatedAction || !action) {
265
+ return false;
266
+ }
267
+ const layer = this.context.themesHelper.findLayerByName(action.data);
268
+ return !!layer;
269
+ }
233
270
  onMouseOver(result) {
234
271
  this.focusResult(result);
235
272
  }
@@ -249,7 +286,7 @@ ${this.htmlUnsafe(this.feedbackTemplateHtml ?? '')}`;
249
286
  if (this.focusedResult) {
250
287
  this.focusedResult.set('selected', false);
251
288
  }
252
- // Set new selected object, and activate preview
289
+ // Set the newly selected object and activate a preview.
253
290
  this.focusedResultIndex = this.allResults.indexOf(result);
254
291
  this.focusedResult = this.allResults[this.focusedResultIndex];
255
292
  this.focusedResult.set('selected', true);
@@ -265,7 +302,7 @@ ${this.htmlUnsafe(this.feedbackTemplateHtml ?? '')}`;
265
302
  this.addFeatureToPreview(result);
266
303
  this.updatePreviewLayerStyle();
267
304
  }
268
- const firstAction = result.get('actions')?.[0];
305
+ const firstAction = this.getFeatureFirstAction(result);
269
306
  if (firstAction?.action.startsWith('add_layer') && this.context.configManager.Config.search?.layerPreview) {
270
307
  const layer = this.context.themesHelper.findLayerByName(firstAction.data);
271
308
  if (layer) {
@@ -294,11 +331,7 @@ ${this.htmlUnsafe(this.feedbackTemplateHtml ?? '')}`;
294
331
  }
295
332
  }
296
333
  onSelect(feature) {
297
- this.selectedResult = feature;
298
- this.ignoreBlur = false;
299
- this.forceHide = true;
300
- this.previewLayers = undefined;
301
- super.render();
334
+ this.closeDropdown(feature);
302
335
  // If the result has a specific zoom-to extent defined or contains a geometry, zoom to it
303
336
  const zoomBbox = this.context.searchManager.getZoomToBbox(feature);
304
337
  if (zoomBbox) {
@@ -308,33 +341,46 @@ ${this.htmlUnsafe(this.feedbackTemplateHtml ?? '')}`;
308
341
  this.addResultToTreeView(feature);
309
342
  }
310
343
  this.onFocusOut();
311
- // Update searchbox with result
344
+ this.updateSearchboxWithResult(feature);
345
+ }
346
+ closeDropdown(feature) {
347
+ this.selectedResult = feature;
348
+ this.ignoreBlur = false;
349
+ this.forceHide = true;
350
+ this.previewLayers = undefined;
351
+ super.render();
352
+ }
353
+ updateSearchboxWithResult(feature) {
312
354
  if (this.searchInput && feature.get('label')) {
313
355
  this.searchInput.value = feature.get('label');
314
356
  }
315
357
  }
316
- addResultToTreeView(feature) {
317
- let clonedTheme;
358
+ getFeatureFirstAction(feature) {
318
359
  const actions = feature.get('actions');
319
- if (!actions || actions.length === 0) {
360
+ return actions?.length ? actions[0] : undefined;
361
+ }
362
+ addResultToTreeView(feature) {
363
+ const action = this.getFeatureFirstAction(feature);
364
+ if (!action) {
320
365
  // Nothing to add
321
366
  return;
322
367
  }
368
+ let clonedTheme;
323
369
  let activate = false;
324
- if (actions[0].action.startsWith('add_theme')) {
325
- const theme = this.context.themesHelper.findThemeByName(actions[0].data);
370
+ if (action.action.startsWith('add_theme')) {
371
+ const theme = this.context.themesHelper.findThemeByName(action.data);
326
372
  if (theme) {
327
373
  clonedTheme = theme.clone();
328
374
  }
329
375
  }
330
- else if (actions[0].action.startsWith('add_group')) {
331
- const group = this.context.themesHelper.findGroupByName(actions[0].data);
376
+ else if (action.action.startsWith('add_group')) {
377
+ const group = this.context.themesHelper.findGroupByName(action.data);
332
378
  if (group) {
333
379
  clonedTheme = this.context.themesHelper.getMinimalClonedThemeForLayer(group);
334
380
  }
335
381
  }
336
- else if (actions[0].action.startsWith('add_layer')) {
337
- const layer = this.context.themesHelper.findLayerByName(actions[0].data);
382
+ else if (action.action.startsWith('add_layer')) {
383
+ const layer = this.context.themesHelper.findLayerByName(action.data);
338
384
  if (layer) {
339
385
  clonedTheme = this.context.themesHelper.getMinimalClonedThemeForLayer(layer);
340
386
  activate = true;
@@ -516,6 +562,9 @@ ${this.htmlUnsafe(this.feedbackTemplateHtml ?? '')}`;
516
562
  return `data:image/svg+xml;utf8,${encodeURIComponent(pin)}`;
517
563
  }
518
564
  }
565
+ __decorate([
566
+ UsedInTemplateOnly()
567
+ ], SearchComponent.prototype, "addFullThemeIfNotPresent", null);
519
568
  __decorate([
520
569
  UsedInTemplateOnly()
521
570
  ], SearchComponent.prototype, "getGroupIcon", null);
@@ -528,4 +577,7 @@ __decorate([
528
577
  __decorate([
529
578
  UsedInTemplateOnly()
530
579
  ], SearchComponent.prototype, "getLabel", null);
580
+ __decorate([
581
+ UsedInTemplateOnly()
582
+ ], SearchComponent.prototype, "isFeatureActionThemeRelated", null);
531
583
  export default SearchComponent;