@indiscale/linkahead-webui-ext-map 0.5.0

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 (76) hide show
  1. package/.eslintrc.json +45 -0
  2. package/.gitlab-ci.yml +44 -0
  3. package/CHANGELOG.md +78 -0
  4. package/README.md +97 -0
  5. package/RELEASE_GUIDELINES.md +45 -0
  6. package/__mocks__/fileMock.js +3 -0
  7. package/__mocks__/styleMock.js +1 -0
  8. package/babel.config.js +22 -0
  9. package/cypress/e2e/standalone-map.cy.js +55 -0
  10. package/cypress/support/commands.js +25 -0
  11. package/cypress/support/e2e.js +17 -0
  12. package/cypress.config.js +10 -0
  13. package/dist/2b3e1faf89f94a483539.png +0 -0
  14. package/dist/416d91365b44e4b4f477.png +0 -0
  15. package/dist/8f2c4d11474275fbc161.png +0 -0
  16. package/dist/index.html +1 -0
  17. package/dist/linkahead-webui-ext-map.js +3 -0
  18. package/dist/linkahead-webui-ext-map.js.LICENSE.txt +45 -0
  19. package/dist/linkahead-webui-ext-map.js.map +1 -0
  20. package/iframe/index.html +6 -0
  21. package/indiscale-linkahead-webui-ext-map-0.4.1.tgz +0 -0
  22. package/jest.config.js +23 -0
  23. package/jest.setup.js +2 -0
  24. package/package.json +105 -0
  25. package/public/favicon.ico +0 -0
  26. package/public/index.html +11 -0
  27. package/public/logo192.png +0 -0
  28. package/public/logo512.png +0 -0
  29. package/public/manifest.json +25 -0
  30. package/public/map_tile_caosdb_logo.png +0 -0
  31. package/public/mock.js +41 -0
  32. package/public/robots.txt +3 -0
  33. package/select_query.json +3 -0
  34. package/src/AllMapEntities.tsx +294 -0
  35. package/src/CurrentPageEntities.js +318 -0
  36. package/src/Map.helpers.css +8 -0
  37. package/src/Map.helpers.js +536 -0
  38. package/src/Map.js +288 -0
  39. package/src/Map.test.js +252 -0
  40. package/src/MapConfig.js +75 -0
  41. package/src/__snapshots__/Map.test.js.snap +1725 -0
  42. package/src/components/Coordinates.js +24 -0
  43. package/src/components/ErrorComponent.tsx +2 -0
  44. package/src/components/Graticule.js +27 -0
  45. package/src/components/Loader.module.css +17 -0
  46. package/src/components/Loader.tsx +36 -0
  47. package/src/components/PathDropDown.js +108 -0
  48. package/src/components/SearchControl.js +502 -0
  49. package/src/components/ToggleMapButton.js +194 -0
  50. package/src/components/ViewChangeControl.js +104 -0
  51. package/src/constants/index.js +1 -0
  52. package/src/context/ConfigProvider.test.js +232 -0
  53. package/src/context/ConfigProvider.tsx +189 -0
  54. package/src/context/LoadingProvider.test.js +124 -0
  55. package/src/context/LoadingProvider.tsx +117 -0
  56. package/src/context/PathIdProvider.js +102 -0
  57. package/src/contrib/latlnggraticule/LICENSE +20 -0
  58. package/src/contrib/latlnggraticule/README.md +68 -0
  59. package/src/contrib/latlnggraticule/leaflet.latlng-graticule.js +528 -0
  60. package/src/contrib/simplegraticule/L.Graticule.js +138 -0
  61. package/src/default_config.json +57 -0
  62. package/src/global.d.ts +8 -0
  63. package/src/index.js +6 -0
  64. package/src/index.scss +133 -0
  65. package/src/logging.js +7 -0
  66. package/src/renderHtmlTemplate.test.js +60 -0
  67. package/src/select-search.min.svg +1 -0
  68. package/src/select-search.svg +46 -0
  69. package/src/setupTests.js +5 -0
  70. package/src/utils/GenerateQueryString.js +200 -0
  71. package/src/utils/GenerateQueryString.test.js +304 -0
  72. package/src/utils/index.ts +3 -0
  73. package/standalone.config.js +5 -0
  74. package/static/map_tile_caosdb_logo.png +0 -0
  75. package/tsconfig.json +25 -0
  76. package/webpack.config.js +193 -0
package/src/Map.js ADDED
@@ -0,0 +1,288 @@
1
+ import { useCallback, useState, useEffect } from "react";
2
+ import PropTypes from "prop-types";
3
+ import {
4
+ LayersControl,
5
+ MapContainer,
6
+ TileLayer,
7
+ WMSTileLayer,
8
+ useMap,
9
+ } from "react-leaflet";
10
+ import { ConfigProvider, useConfig } from "./context/ConfigProvider";
11
+ import { PathIdProvider, usePathId } from "./context/PathIdProvider";
12
+ import { LoadingProvider, useLoading } from "./context/LoadingProvider";
13
+ import L from "leaflet";
14
+ import LProj from "proj4leaflet";
15
+ import { CurrentPageEntities } from "./CurrentPageEntities";
16
+ import { AllMapEntities } from "./AllMapEntities";
17
+ import { get_map_config } from "./MapConfig";
18
+ import { ViewChangeControl } from "./components/ViewChangeControl";
19
+ import { SearchControl } from "./components/SearchControl";
20
+ import { Coordinates } from "./components/Coordinates";
21
+ import { Graticule } from "./components/Graticule";
22
+ import { PathDropDown } from "./components/PathDropDown";
23
+ import { Await } from "@indiscale/linkahead-webui-core-components";
24
+ import { logger } from "./logging";
25
+ import { Loader } from "./components/Loader";
26
+
27
+ L.Proj = L.Proj || LProj;
28
+
29
+ /**
30
+ * Create a Leaflet tile layer.
31
+ *
32
+ * @param {TileLayerConfig} config - configuration for the map.
33
+ *
34
+ * @returns {L.TileLayer} the tile layer
35
+ */
36
+ const create_tile_layer = function (config) {
37
+ logger.trace("enter create_tile_layer", config);
38
+
39
+ // set tiling server
40
+ config.options = config.options || {};
41
+ if (config.type && config.type === "wms") {
42
+ return <WMSTileLayer url={config.url} {...config.options} />;
43
+ } else if (config.type === "osm" || typeof config.type === "undefined") {
44
+ return <TileLayer url={config.url} {...config.options} />;
45
+ } else {
46
+ throw new Error("unknown tileLayer type: " + config.type);
47
+ }
48
+ };
49
+
50
+ function get_crs_config(config) {
51
+ var crs = L.CRS.EPSG3857;
52
+ if (typeof config.crs === "string" || config.crs instanceof String) {
53
+ crs = L.CRS[config.crs.replace(":", "")];
54
+ logger.debug("use pre-defined CRS ", crs);
55
+ } else if (typeof config.crs === "object") {
56
+ crs = new L.Proj.CRS(
57
+ config.crs.code,
58
+ config.crs.proj4def,
59
+ config.crs.options
60
+ );
61
+ logger.debug("use custom proj4 CRS ", crs);
62
+ }
63
+ return crs;
64
+ }
65
+
66
+ function ResizeListener() {
67
+ const map = useMap();
68
+
69
+ useEffect(() => {
70
+ map
71
+ .getContainer()
72
+ .parentElement.addEventListener(
73
+ "caosdb-webui-ext-map.after-toggle",
74
+ (e) => {
75
+ logger.trace("ResizeListener", e);
76
+ map.invalidateSize(true);
77
+ }
78
+ );
79
+ }, [map]);
80
+
81
+ return null;
82
+ }
83
+
84
+ function EntityLayers({ queryCallback, standaloneConfig }) {
85
+ // Get config from Provider
86
+ const { config } = useConfig();
87
+ // Use context / provider to save current Path
88
+ const { pathId, setPathId } = usePathId();
89
+ // Spinner shown using global loading state in context
90
+ const { loading } = useLoading();
91
+
92
+ const { hidePathSwitch, hideLayerSwitch, hideQuerySelector } =
93
+ standaloneConfig || {};
94
+
95
+ // Have to use a CSS hack to hide the actual control
96
+ // as hiding the Component stops the pins being displayed.
97
+ useEffect(() => {
98
+ const layersControlElement = document.querySelector(
99
+ ".leaflet-control-layers"
100
+ );
101
+ if (layersControlElement) {
102
+ layersControlElement.style.display = hideLayerSwitch ? "none" : "block";
103
+ }
104
+ }, [hideLayerSwitch]);
105
+
106
+ const currentPath = config.select.paths[pathId] || [];
107
+ logger.trace("EntityLayers", queryCallback, config, pathId, currentPath);
108
+
109
+ useEffect(() => {
110
+ if (pathId !== "same") {
111
+ sessionStorage.setItem("caosdb_map.display_path", pathId);
112
+ } else {
113
+ sessionStorage.removeItem("caosdb_map.display_path");
114
+ }
115
+ }, [pathId]);
116
+
117
+ return (
118
+ <>
119
+ {!hideQuerySelector && (
120
+ <SearchControl
121
+ queryCallback={queryCallback}
122
+ currentPath={currentPath}
123
+ />
124
+ )}
125
+ <LayersControl>
126
+ {loading && <Loader dataTestId="mapLoader" inMap />}
127
+ {!config.entityLayers.current_page_entities || <CurrentPageEntities />}
128
+ {!config.entityLayers.all_map_entities || (
129
+ <AllMapEntities hideLayerSwitch={hideLayerSwitch} />
130
+ )}
131
+ </LayersControl>
132
+
133
+ {!hidePathSwitch && (
134
+ <PathDropDown currentPath={pathId} setCurrentPath={setPathId} />
135
+ )}
136
+ </>
137
+ );
138
+ }
139
+
140
+ EntityLayers.propTypes = {
141
+ queryCallback: PropTypes.func,
142
+ standaloneConfig: PropTypes.shape({
143
+ hidePathSwitch: PropTypes.bool,
144
+ hideLayerSwitch: PropTypes.bool,
145
+ hideQuerySelector: PropTypes.bool,
146
+ }),
147
+ };
148
+
149
+ function ConfigureMap({ initialView, queryCallback, standaloneConfig }) {
150
+ const { config } = useConfig();
151
+ logger.trace(config, initialView, queryCallback);
152
+ const [mapView, setMapView] = useState(initialView || config.default_view);
153
+ const _setMapView = useCallback(
154
+ (view) => {
155
+ logger.trace("_setMapView", view);
156
+ if (view) {
157
+ sessionStorage.setItem("caosdb_map.view", JSON.stringify(view));
158
+ } else {
159
+ sessionStorage.removeItem("caosdb_map.view");
160
+ }
161
+ setMapView(view || config.default_view);
162
+ },
163
+ [setMapView, config.default_view]
164
+ );
165
+
166
+ if (
167
+ config.disabled ||
168
+ window.localStorage.edit_mode === "true" ||
169
+ sessionStorage.getItem("caosdb_map.show") === "false"
170
+ ) {
171
+ logger.trace("map not rendered, disabled or in edit mode");
172
+ return <div />;
173
+ }
174
+
175
+ var view_config = undefined;
176
+ config.views.forEach((view) => {
177
+ if (view.id === mapView) {
178
+ view_config = view;
179
+ }
180
+ });
181
+
182
+ const tileLayer = create_tile_layer(view_config.tileLayer);
183
+
184
+ const map_options = {};
185
+ map_options.boxZoom = view_config.boxZoom || false;
186
+ map_options.center = view_config.center || [0, 0];
187
+ map_options.zoom = view_config.zoom || 10;
188
+
189
+ const crs_config = get_crs_config(view_config);
190
+ if (crs_config) {
191
+ map_options.crs = crs_config;
192
+ }
193
+ return (
194
+ <MapContainer
195
+ key={mapView}
196
+ className="map"
197
+ scrollWheelZoom={true}
198
+ {...map_options}
199
+ >
200
+ {tileLayer}
201
+ <ResizeListener />
202
+ <Coordinates />
203
+ {view_config.graticule && <Graticule config={view_config.graticule} />}
204
+ <ViewChangeControl mapView={mapView} setMapView={_setMapView} />
205
+ <EntityLayers
206
+ queryCallback={queryCallback}
207
+ config={config}
208
+ standaloneConfig={standaloneConfig}
209
+ />
210
+ </MapContainer>
211
+ );
212
+ }
213
+
214
+ ConfigureMap.propTypes = {
215
+ queryCallback: PropTypes.func,
216
+ initialView: PropTypes.string,
217
+ standaloneConfig: PropTypes.shape({
218
+ hidePathSwitch: PropTypes.bool,
219
+ hideLayerSwitch: PropTypes.bool,
220
+ hideQuerySelector: PropTypes.bool,
221
+ }),
222
+ };
223
+
224
+ ConfigureMap.defaultProps = {
225
+ queryCallback: (query) => {
226
+ logger.error("Unconfigured queryCallback", query);
227
+ },
228
+ config: undefined,
229
+ };
230
+
231
+ const SaveMapConfigToContext = (props) => {
232
+ // mapConfigFromApi needed only in this component
233
+ // the rest will be passed onwards
234
+ const { mapConfigFromApi, ...restProps } = props;
235
+ const { config, setConfig } = useConfig();
236
+
237
+ // Save Config in ConfigProvider to make accessible in whole App
238
+ useEffect(() => {
239
+ setConfig(mapConfigFromApi);
240
+ }, [mapConfigFromApi, setConfig]);
241
+
242
+ // And now all underlying components use the config stored in the context
243
+ // be it through passing props or the useConfig hook
244
+ return config ? <ConfigureMap {...restProps} /> : null;
245
+ };
246
+ SaveMapConfigToContext.propTypes = {
247
+ mapConfigFromApi: PropTypes.shape({
248
+ datamodel: PropTypes.object.isRequired,
249
+ select: PropTypes.object.isRequired,
250
+ entityLayers: PropTypes.object.isRequired,
251
+ iframeSettings: PropTypes.shape({
252
+ formatString: PropTypes.string,
253
+ }),
254
+ }),
255
+ };
256
+ export function Map({ queryCallback, initialView, standaloneConfig }) {
257
+ return (
258
+ <PathIdProvider>
259
+ <ConfigProvider>
260
+ <LoadingProvider>
261
+ <Await
262
+ loading={<Loader dataTestId="mapLoader" />}
263
+ promise={get_map_config()}
264
+ then={(mapConfigFromApi) => (
265
+ <SaveMapConfigToContext
266
+ mapConfigFromApi={mapConfigFromApi}
267
+ initialView={initialView}
268
+ queryCallback={queryCallback}
269
+ standaloneConfig={standaloneConfig}
270
+ />
271
+ )}
272
+ />
273
+ </LoadingProvider>
274
+ </ConfigProvider>
275
+ </PathIdProvider>
276
+ );
277
+ }
278
+
279
+ Map.propTypes = {
280
+ queryCallback: PropTypes.func,
281
+ initialView: PropTypes.string,
282
+ pathSwitch: PropTypes.bool,
283
+ standaloneConfig: PropTypes.shape({
284
+ hidePathSwitch: PropTypes.bool,
285
+ hideLayerSwitch: PropTypes.bool,
286
+ hideQuerySelector: PropTypes.bool,
287
+ }),
288
+ };
@@ -0,0 +1,252 @@
1
+ import React from "react";
2
+ import default_config from "./default_config.json";
3
+ import { get_map_config } from "./MapConfig";
4
+ import { act, render, screen } from "@testing-library/react";
5
+ import "@testing-library/jest-dom/extend-expect";
6
+ import { Map } from "./Map";
7
+
8
+ jest.mock("./MapConfig");
9
+ jest.mock("./CurrentPageEntities", () => {
10
+ return {
11
+ __esModule: true,
12
+ CurrentPageEntities: () => {
13
+ return <div data-testid="currect-page-entities" />;
14
+ },
15
+ };
16
+ });
17
+ jest.mock("./AllMapEntities", () => {
18
+ return {
19
+ __esModule: true,
20
+ AllMapEntities: () => {
21
+ return <div data-testid="all-map-entities" />;
22
+ },
23
+ };
24
+ });
25
+
26
+ const sleep = function (ms) {
27
+ return new Promise((resolve) => {
28
+ setTimeout(resolve, ms);
29
+ });
30
+ };
31
+
32
+ describe("Map basic properties", () => {
33
+ test("shows loading until config is there", async () => {
34
+ get_map_config.mockReturnValue(
35
+ new Promise((resolve) => {
36
+ setTimeout(() => resolve(default_config), 1);
37
+ })
38
+ );
39
+
40
+ const { container } = render(<Map />);
41
+ expect(container).toMatchSnapshot();
42
+
43
+ var spinner = screen.getByTitle(/Loading/i);
44
+ expect(spinner).toBeInTheDocument();
45
+ expect(container.firstChild.className).toBe("loader loaderFullScreen");
46
+
47
+ await act(async () => {
48
+ await sleep(1000);
49
+ });
50
+
51
+ spinner = screen.queryByTitle(/Loading/i);
52
+ expect(spinner).toBeNull();
53
+
54
+ expect(container.firstChild.className).toContain("map");
55
+ expect(container.firstChild.className).toContain("leaflet-container");
56
+ });
57
+ test("Shows layers control", async () => {
58
+ get_map_config.mockReturnValue(
59
+ new Promise((resolve) => {
60
+ setTimeout(() => resolve(default_config), 1);
61
+ })
62
+ );
63
+
64
+ const { container } = render(<Map />);
65
+ await act(async () => {
66
+ await sleep(1000);
67
+ });
68
+
69
+ const layersControl = container.querySelector(".leaflet-control-layers");
70
+ expect(layersControl).toBeInTheDocument();
71
+ });
72
+
73
+ test("Shows search control", async () => {
74
+ get_map_config.mockReturnValue(
75
+ new Promise((resolve) => {
76
+ setTimeout(() => resolve(default_config), 1);
77
+ })
78
+ );
79
+
80
+ const { container } = render(<Map />);
81
+ await act(async () => {
82
+ await sleep(1000);
83
+ });
84
+
85
+ const searchControl = container.querySelector(
86
+ ".caosdb-f-map-select-search-btn"
87
+ );
88
+ expect(searchControl).toBeInTheDocument();
89
+ });
90
+
91
+ test("Shows path switch", async () => {
92
+ get_map_config.mockReturnValue(
93
+ new Promise((resolve) => {
94
+ setTimeout(() => resolve(default_config), 1);
95
+ })
96
+ );
97
+
98
+ const { container } = render(<Map />);
99
+ await act(async () => {
100
+ await sleep(1000);
101
+ });
102
+
103
+ const pathSwitch = container.querySelector(".caosdb-f-map-path-drop-down");
104
+ expect(pathSwitch).toBeInTheDocument();
105
+ });
106
+ });
107
+ describe("Map basic properties in Standalone mode", () => {
108
+ test("shows loading until config is there", async () => {
109
+ get_map_config.mockReturnValue(
110
+ new Promise((resolve) => {
111
+ setTimeout(() => resolve(default_config), 1);
112
+ })
113
+ );
114
+ const standaloneConfig = {
115
+ hideLayerSwitch: true,
116
+ hideQuerySelector: true,
117
+ hidePathSwitch: true,
118
+ };
119
+ const { container } = render(<Map standaloneConfig={standaloneConfig} />);
120
+
121
+ var spinner = screen.getByTitle(/Loading/i);
122
+ expect(spinner).toBeInTheDocument();
123
+ expect(container.firstChild.className).toBe("loader loaderFullScreen");
124
+
125
+ await act(async () => {
126
+ await sleep(1000);
127
+ });
128
+ expect(container).toMatchSnapshot();
129
+ spinner = screen.queryByTitle(/Loading/i);
130
+ expect(spinner).toBeNull();
131
+
132
+ expect(container.firstChild.className).toContain("map");
133
+ expect(container.firstChild.className).toContain("leaflet-container");
134
+ });
135
+
136
+ test("Hides layers control when in config", async () => {
137
+ get_map_config.mockReturnValue(
138
+ new Promise((resolve) => {
139
+ setTimeout(() => resolve(default_config), 1);
140
+ })
141
+ );
142
+ const standaloneConfig = {
143
+ hideLayerSwitch: true,
144
+ };
145
+ const { container } = render(<Map standaloneConfig={standaloneConfig} />);
146
+
147
+ await act(async () => {
148
+ await sleep(1000);
149
+ });
150
+ expect(container).toMatchSnapshot();
151
+ expect(
152
+ container.querySelector(".leaflet-control-layers")
153
+ ).not.toBeVisible();
154
+ });
155
+
156
+ test("Shows layers control when in config", async () => {
157
+ get_map_config.mockReturnValue(
158
+ new Promise((resolve) => {
159
+ setTimeout(() => resolve(default_config), 1);
160
+ })
161
+ );
162
+ const standaloneConfig = {
163
+ hideLayerSwitch: false,
164
+ };
165
+ const { container } = render(<Map standaloneConfig={standaloneConfig} />);
166
+
167
+ await act(async () => {
168
+ await sleep(1000);
169
+ });
170
+ expect(container).toMatchSnapshot();
171
+ expect(container.querySelector(".leaflet-control-layers")).toBeVisible();
172
+ });
173
+
174
+ test("Hides search/query control when in config", async () => {
175
+ get_map_config.mockReturnValue(
176
+ new Promise((resolve) => {
177
+ setTimeout(() => resolve(default_config), 1);
178
+ })
179
+ );
180
+ const standaloneConfig = {
181
+ hideQuerySelector: true,
182
+ };
183
+ const { container } = render(<Map standaloneConfig={standaloneConfig} />);
184
+
185
+ await act(async () => {
186
+ await sleep(1000);
187
+ });
188
+ expect(container).toMatchSnapshot();
189
+ expect(
190
+ container.querySelector(".caosdb-f-map-select-search-btn")
191
+ ).not.toBeInTheDocument();
192
+ });
193
+
194
+ test("Shows search/query control when in config", async () => {
195
+ get_map_config.mockReturnValue(
196
+ new Promise((resolve) => {
197
+ setTimeout(() => resolve(default_config), 1);
198
+ })
199
+ );
200
+ const standaloneConfig = {
201
+ hideQuerySelector: false,
202
+ };
203
+ const { container } = render(<Map standaloneConfig={standaloneConfig} />);
204
+ await act(async () => {
205
+ await sleep(1000);
206
+ });
207
+ expect(container).toMatchSnapshot();
208
+ expect(
209
+ container.querySelector(".caosdb-f-map-select-search-btn")
210
+ ).toBeInTheDocument();
211
+ });
212
+
213
+ test("Hides path switch when in config", async () => {
214
+ get_map_config.mockReturnValue(
215
+ new Promise((resolve) => {
216
+ setTimeout(() => resolve(default_config), 1);
217
+ })
218
+ );
219
+ const standaloneConfig = {
220
+ hidePathSwitch: true,
221
+ };
222
+ const { container } = render(<Map standaloneConfig={standaloneConfig} />);
223
+
224
+ await act(async () => {
225
+ await sleep(1000);
226
+ });
227
+ expect(container).toMatchSnapshot();
228
+ expect(
229
+ container.querySelector(".caosdb-f-map-path-drop-down")
230
+ ).not.toBeInTheDocument();
231
+ });
232
+
233
+ test("Shows path switch when in config", async () => {
234
+ get_map_config.mockReturnValue(
235
+ new Promise((resolve) => {
236
+ setTimeout(() => resolve(default_config), 1);
237
+ })
238
+ );
239
+ const standaloneConfig = {
240
+ hidePathSwitch: false,
241
+ };
242
+ const { container } = render(<Map standaloneConfig={standaloneConfig} />);
243
+
244
+ await act(async () => {
245
+ await sleep(1000);
246
+ });
247
+ expect(container).toMatchSnapshot();
248
+ expect(
249
+ container.querySelector(".caosdb-f-map-path-drop-down")
250
+ ).toBeInTheDocument();
251
+ });
252
+ });
@@ -0,0 +1,75 @@
1
+ import { logger } from "./logging";
2
+ import default_config from "./default_config.json";
3
+
4
+ const fetch_config = async function () {
5
+ try {
6
+ let response;
7
+ // If developing or using in Standalone Mode, Linkahead would normally be running
8
+ // on a different port / URL so we need to fetch the config from there.
9
+ if (process.env.STANDALONE_MODE || process.env.DEVELOPMENT_MODE) {
10
+ response = await fetch(
11
+ `${process.env.LINKAHEAD_URL}/webinterface/conf/json/ext_map.json`
12
+ );
13
+ } else {
14
+ response = await fetch("/webinterface/conf/json/ext_map.json");
15
+ }
16
+ if (response.status === 404) {
17
+ logger.warn("couldn't find the map config file");
18
+ return {};
19
+ }
20
+ const local_config = await response.json();
21
+ return local_config;
22
+ } catch (err) {
23
+ logger.error(err);
24
+ return {};
25
+ }
26
+ };
27
+
28
+ const load_map_config = async function () {
29
+ const remote_config = await fetch_config();
30
+ const local_config = get_local_config();
31
+
32
+ // TODO implement merge, not concat (because deep properties will be lost).
33
+ const map_config = { ...default_config, ...remote_config, ...local_config };
34
+ return map_config;
35
+ };
36
+
37
+ const get_from_session_storage = function (key) {
38
+ try {
39
+ let val = sessionStorage.getItem(key);
40
+ if (val && val !== "undefined") {
41
+ try {
42
+ return JSON.parse(val); // return parsed value when is JSON
43
+ } catch {
44
+ return val; // fallback: otherwise return String
45
+ }
46
+ }
47
+ } catch (err) {
48
+ logger.error(err);
49
+ }
50
+ return undefined;
51
+ };
52
+
53
+ export function get_local_config() {
54
+ const result = {};
55
+ let val = get_from_session_storage("caosdb_map.view");
56
+ if (val) {
57
+ result["view"] = val;
58
+ }
59
+
60
+ val = get_from_session_storage("caosdb_map.show");
61
+ if (val) {
62
+ result["show"] = val;
63
+ }
64
+
65
+ return result;
66
+ }
67
+
68
+ var map_config = undefined;
69
+
70
+ export async function get_map_config() {
71
+ if (!map_config) {
72
+ map_config = await load_map_config();
73
+ }
74
+ return map_config;
75
+ }