@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.
- package/.eslintrc.json +45 -0
- package/.gitlab-ci.yml +44 -0
- package/CHANGELOG.md +78 -0
- package/README.md +97 -0
- package/RELEASE_GUIDELINES.md +45 -0
- package/__mocks__/fileMock.js +3 -0
- package/__mocks__/styleMock.js +1 -0
- package/babel.config.js +22 -0
- package/cypress/e2e/standalone-map.cy.js +55 -0
- package/cypress/support/commands.js +25 -0
- package/cypress/support/e2e.js +17 -0
- package/cypress.config.js +10 -0
- package/dist/2b3e1faf89f94a483539.png +0 -0
- package/dist/416d91365b44e4b4f477.png +0 -0
- package/dist/8f2c4d11474275fbc161.png +0 -0
- package/dist/index.html +1 -0
- package/dist/linkahead-webui-ext-map.js +3 -0
- package/dist/linkahead-webui-ext-map.js.LICENSE.txt +45 -0
- package/dist/linkahead-webui-ext-map.js.map +1 -0
- package/iframe/index.html +6 -0
- package/indiscale-linkahead-webui-ext-map-0.4.1.tgz +0 -0
- package/jest.config.js +23 -0
- package/jest.setup.js +2 -0
- package/package.json +105 -0
- package/public/favicon.ico +0 -0
- package/public/index.html +11 -0
- package/public/logo192.png +0 -0
- package/public/logo512.png +0 -0
- package/public/manifest.json +25 -0
- package/public/map_tile_caosdb_logo.png +0 -0
- package/public/mock.js +41 -0
- package/public/robots.txt +3 -0
- package/select_query.json +3 -0
- package/src/AllMapEntities.tsx +294 -0
- package/src/CurrentPageEntities.js +318 -0
- package/src/Map.helpers.css +8 -0
- package/src/Map.helpers.js +536 -0
- package/src/Map.js +288 -0
- package/src/Map.test.js +252 -0
- package/src/MapConfig.js +75 -0
- package/src/__snapshots__/Map.test.js.snap +1725 -0
- package/src/components/Coordinates.js +24 -0
- package/src/components/ErrorComponent.tsx +2 -0
- package/src/components/Graticule.js +27 -0
- package/src/components/Loader.module.css +17 -0
- package/src/components/Loader.tsx +36 -0
- package/src/components/PathDropDown.js +108 -0
- package/src/components/SearchControl.js +502 -0
- package/src/components/ToggleMapButton.js +194 -0
- package/src/components/ViewChangeControl.js +104 -0
- package/src/constants/index.js +1 -0
- package/src/context/ConfigProvider.test.js +232 -0
- package/src/context/ConfigProvider.tsx +189 -0
- package/src/context/LoadingProvider.test.js +124 -0
- package/src/context/LoadingProvider.tsx +117 -0
- package/src/context/PathIdProvider.js +102 -0
- package/src/contrib/latlnggraticule/LICENSE +20 -0
- package/src/contrib/latlnggraticule/README.md +68 -0
- package/src/contrib/latlnggraticule/leaflet.latlng-graticule.js +528 -0
- package/src/contrib/simplegraticule/L.Graticule.js +138 -0
- package/src/default_config.json +57 -0
- package/src/global.d.ts +8 -0
- package/src/index.js +6 -0
- package/src/index.scss +133 -0
- package/src/logging.js +7 -0
- package/src/renderHtmlTemplate.test.js +60 -0
- package/src/select-search.min.svg +1 -0
- package/src/select-search.svg +46 -0
- package/src/setupTests.js +5 -0
- package/src/utils/GenerateQueryString.js +200 -0
- package/src/utils/GenerateQueryString.test.js +304 -0
- package/src/utils/index.ts +3 -0
- package/standalone.config.js +5 -0
- package/static/map_tile_caosdb_logo.png +0 -0
- package/tsconfig.json +25 -0
- package/webpack.config.js +193 -0
|
@@ -0,0 +1,502 @@
|
|
|
1
|
+
import PropTypes from "prop-types";
|
|
2
|
+
import { logger } from "../logging";
|
|
3
|
+
import L from "leaflet";
|
|
4
|
+
import { useMap } from "react-leaflet";
|
|
5
|
+
import { get_with_POV } from "../Map.helpers";
|
|
6
|
+
import { useConfig } from "../context/ConfigProvider";
|
|
7
|
+
import SelectSearchIcon from "../select-search.min.svg";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Plug-in for leaflet which lets the user select an area in the map
|
|
11
|
+
* and execute a query using a latitude/longitude filter for entities.
|
|
12
|
+
*
|
|
13
|
+
* This handler adds a select button as a control to the map. The
|
|
14
|
+
* select button toggles and indicates the select mode (on/off).
|
|
15
|
+
*
|
|
16
|
+
* The selection can be started by either clicking on the map when the
|
|
17
|
+
* select mode is on (after enabling it via the select button).
|
|
18
|
+
*
|
|
19
|
+
* The query button apears as soon as an selection has being finished.
|
|
20
|
+
*
|
|
21
|
+
* The query button generates a query from for searching inside the
|
|
22
|
+
* selected area and calls the `queryCallback` function.
|
|
23
|
+
*/
|
|
24
|
+
const select_handler = {
|
|
25
|
+
/**
|
|
26
|
+
* Initialize the handler after it has been added to the map.
|
|
27
|
+
*
|
|
28
|
+
* This function is called by the map after the handler has been
|
|
29
|
+
* added via {@link L.Map.addHandler} or the handler added itself
|
|
30
|
+
* to the map via {@link L.Handler.addTo}.
|
|
31
|
+
*
|
|
32
|
+
* This method
|
|
33
|
+
* 1) Adds the select button.
|
|
34
|
+
* 2) Adds a listener for the `mousedown` event.
|
|
35
|
+
*
|
|
36
|
+
* Both are means to start the process of selecting an area in the
|
|
37
|
+
* map.
|
|
38
|
+
*/
|
|
39
|
+
addHooks: function () {
|
|
40
|
+
const select_button = this._get_select_button((event) => {
|
|
41
|
+
logger.trace("select button clicked", this);
|
|
42
|
+
event.preventDefault();
|
|
43
|
+
event.stopPropagation();
|
|
44
|
+
this._toggle_select_mode();
|
|
45
|
+
});
|
|
46
|
+
this._select_button = select_button;
|
|
47
|
+
this._map.addControl(select_button);
|
|
48
|
+
this._map.on("mousedown", this._mousedown_listener);
|
|
49
|
+
},
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Clean up after the select handler has been removed from the map.
|
|
53
|
+
*
|
|
54
|
+
* This method removes the select button and the `mousedown`
|
|
55
|
+
* listener.
|
|
56
|
+
*/
|
|
57
|
+
removeHooks: function () {
|
|
58
|
+
this._select_button.remove();
|
|
59
|
+
this._map.off("mousedown", this._mousedown_listener);
|
|
60
|
+
},
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Change the color of the select button in order to highlight it.
|
|
64
|
+
*
|
|
65
|
+
* This is used to indicate that the select mode is on and that the
|
|
66
|
+
* user can select something by clicking and moving the mouse on
|
|
67
|
+
* the map.
|
|
68
|
+
*/
|
|
69
|
+
_highlight_select_button: function () {
|
|
70
|
+
this._select_button.button.classList.add("highlight");
|
|
71
|
+
},
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Change the color of the select button back to normal.
|
|
75
|
+
*/
|
|
76
|
+
_unhighlight_select_button: function () {
|
|
77
|
+
this._select_button.button.classList.remove("highlight");
|
|
78
|
+
},
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Toggle the select mode (on/off).
|
|
82
|
+
*
|
|
83
|
+
* This includes setting the _select_mode_on to true/false,
|
|
84
|
+
* highlighting/unhighlighting the select button and
|
|
85
|
+
* disabling/enabling the moving of the map center by dragging.
|
|
86
|
+
*/
|
|
87
|
+
_toggle_select_mode: function () {
|
|
88
|
+
logger.trace("toggle select mode", this);
|
|
89
|
+
if (this._select_mode_on) {
|
|
90
|
+
this._unhighlight_select_button();
|
|
91
|
+
this._map.dragging.enable();
|
|
92
|
+
this._reset_selection();
|
|
93
|
+
this._select_mode_on = false;
|
|
94
|
+
} else {
|
|
95
|
+
this._highlight_select_button();
|
|
96
|
+
this._map.dragging.disable();
|
|
97
|
+
this._select_mode_on = true;
|
|
98
|
+
}
|
|
99
|
+
},
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Return a button for toggling and indicating the select mode.
|
|
103
|
+
*
|
|
104
|
+
* The select button shows a litte dashed square as its icon.
|
|
105
|
+
*
|
|
106
|
+
* @param {function} callback - a callback which toggles the select
|
|
107
|
+
* mode.
|
|
108
|
+
* @returns {L.Control} the select button.
|
|
109
|
+
*/
|
|
110
|
+
_get_select_button: function (callback) {
|
|
111
|
+
// TODO flatten the structure of the code and possibly merge it with the query_button code.
|
|
112
|
+
var select_button = L.Control.extend({
|
|
113
|
+
options: {
|
|
114
|
+
position: "topleft",
|
|
115
|
+
},
|
|
116
|
+
|
|
117
|
+
onAdd: function () {
|
|
118
|
+
return this.button;
|
|
119
|
+
},
|
|
120
|
+
|
|
121
|
+
button: (function () {
|
|
122
|
+
var button = L.DomUtil.create(
|
|
123
|
+
"div",
|
|
124
|
+
"leaflet-bar leaflet-control leaflet-control-custom caosdb-f-map-select-search-btn"
|
|
125
|
+
);
|
|
126
|
+
button.title = "Select and area and search.";
|
|
127
|
+
button.addEventListener("click", callback);
|
|
128
|
+
|
|
129
|
+
const icon = L.DomUtil.create("img", "");
|
|
130
|
+
icon.src = SelectSearchIcon;
|
|
131
|
+
button.appendChild(icon);
|
|
132
|
+
|
|
133
|
+
button.onmousedown = (event) => {
|
|
134
|
+
event.stopPropagation();
|
|
135
|
+
};
|
|
136
|
+
button.onmouseup = (event) => {
|
|
137
|
+
event.stopPropagation();
|
|
138
|
+
};
|
|
139
|
+
return button;
|
|
140
|
+
})(),
|
|
141
|
+
});
|
|
142
|
+
return new select_button();
|
|
143
|
+
},
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Return a button for opening the query panel with a pre-filled query.
|
|
147
|
+
*
|
|
148
|
+
* The query button has a loupe icon.
|
|
149
|
+
*
|
|
150
|
+
* The query button is added after an area has been selected and
|
|
151
|
+
* only visible as long an area is selected.
|
|
152
|
+
*
|
|
153
|
+
* @param {function} callback - a callback for opening the query
|
|
154
|
+
* panel and fill in the query.
|
|
155
|
+
* @returns {L.Control} the query button.
|
|
156
|
+
*/
|
|
157
|
+
_get_query_button: function (callback) {
|
|
158
|
+
var query_button = L.Control.extend({
|
|
159
|
+
options: {
|
|
160
|
+
position: "topleft",
|
|
161
|
+
},
|
|
162
|
+
|
|
163
|
+
onAdd: function () {
|
|
164
|
+
return this.button;
|
|
165
|
+
},
|
|
166
|
+
|
|
167
|
+
button: (function () {
|
|
168
|
+
var button = L.DomUtil.create(
|
|
169
|
+
"div",
|
|
170
|
+
"leaflet-bar leaflet-control leaflet-control-custom caosdb-f-map-search-btn"
|
|
171
|
+
);
|
|
172
|
+
button.title = "Search within this area";
|
|
173
|
+
button.innerHTML = '<i class="bi-search"></i>';
|
|
174
|
+
button.onclick = callback;
|
|
175
|
+
|
|
176
|
+
button.onmousedown = (event) => {
|
|
177
|
+
event.stopPropagation();
|
|
178
|
+
};
|
|
179
|
+
button.onmouseup = (event) => {
|
|
180
|
+
event.stopPropagation();
|
|
181
|
+
};
|
|
182
|
+
return button;
|
|
183
|
+
})(),
|
|
184
|
+
});
|
|
185
|
+
return new query_button();
|
|
186
|
+
},
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Listens on the mousedown event of the map and calls the
|
|
190
|
+
* _startSelect method if either (1) the select mode is on or (2)
|
|
191
|
+
* the shift key is pressed during the click on the map.
|
|
192
|
+
*/
|
|
193
|
+
_mousedown_listener: function (event) {
|
|
194
|
+
logger.trace("mousedown", event, "on", this);
|
|
195
|
+
if (!event.originalEvent.shiftKey && !this.select._select_mode_on) {
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
event.originalEvent.preventDefault();
|
|
199
|
+
event.originalEvent.stopPropagation();
|
|
200
|
+
this.select._startSelect(event.latlng);
|
|
201
|
+
},
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Remove a pre-existing selection and start the process of a new
|
|
205
|
+
* selection.
|
|
206
|
+
*
|
|
207
|
+
* When the user clicks on the map with shift key pressed or
|
|
208
|
+
* _select_mode this method is called with the coordinates of the
|
|
209
|
+
* click.
|
|
210
|
+
*
|
|
211
|
+
* This also adds listeners on `mousemove` events (for redrawing
|
|
212
|
+
* the selected area) and on `mouseup` events for finishing the
|
|
213
|
+
* process of selection.
|
|
214
|
+
*
|
|
215
|
+
* @param {L.LatLng} start_point - the coordinates where the
|
|
216
|
+
* selection begins.
|
|
217
|
+
*/
|
|
218
|
+
_startSelect: function (start_point) {
|
|
219
|
+
this._reset_selection();
|
|
220
|
+
this._point1 = start_point;
|
|
221
|
+
logger.trace("point1", this._point1);
|
|
222
|
+
|
|
223
|
+
this._map.on("mousemove", this._drawRect);
|
|
224
|
+
this._map.on("mouseup", this._endSelect);
|
|
225
|
+
},
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* If present, remove the selected area and the query button from
|
|
229
|
+
* the map.
|
|
230
|
+
*/
|
|
231
|
+
_reset_selection: function () {
|
|
232
|
+
this._point1 = undefined;
|
|
233
|
+
if (this._rectangle) {
|
|
234
|
+
this._rectangle.remove();
|
|
235
|
+
}
|
|
236
|
+
this._remove_query_button();
|
|
237
|
+
},
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* (Re-)draw the rectangle which indicates the currently selected
|
|
241
|
+
* area.
|
|
242
|
+
*
|
|
243
|
+
* This method is added as a listener on the `mousemove` event by
|
|
244
|
+
* the _startSelect method.
|
|
245
|
+
*/
|
|
246
|
+
_drawRect: function (event) {
|
|
247
|
+
logger.trace("mousemove", event, "on", this);
|
|
248
|
+
event.originalEvent.preventDefault();
|
|
249
|
+
event.originalEvent.stopPropagation();
|
|
250
|
+
|
|
251
|
+
// remove old rectangle
|
|
252
|
+
if (this.select._rectangle) {
|
|
253
|
+
this.select._rectangle.remove();
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// draw new rectangle
|
|
257
|
+
const point2 = event.latlng;
|
|
258
|
+
const area = this.select._get_area(this.select._point1, point2);
|
|
259
|
+
this.select._rectangle = this.select._get_select_rectangle(area);
|
|
260
|
+
this.select._rectangle.addTo(this);
|
|
261
|
+
},
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* Return a colored rectangle covering an area.
|
|
265
|
+
*
|
|
266
|
+
* @param {L.LatLngBounds} area.
|
|
267
|
+
* @returns {L.Rectangle} the colored rectangle.
|
|
268
|
+
*/
|
|
269
|
+
_get_select_rectangle: function (area) {
|
|
270
|
+
return L.rectangle(area, {
|
|
271
|
+
color: "#ff7800",
|
|
272
|
+
weight: 1,
|
|
273
|
+
});
|
|
274
|
+
},
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* Finish the process of selection, add the query button to the map.
|
|
278
|
+
*
|
|
279
|
+
* This method is a listener on the `mouseup` event and is added by
|
|
280
|
+
* the _startSelect method.
|
|
281
|
+
*
|
|
282
|
+
* It removes itself as a listener and also the _drawRect listener
|
|
283
|
+
* on the `mousemove` event.
|
|
284
|
+
*/
|
|
285
|
+
_endSelect: function (event) {
|
|
286
|
+
logger.trace("mouseup", event, "on", this);
|
|
287
|
+
event.originalEvent.preventDefault();
|
|
288
|
+
event.originalEvent.stopPropagation();
|
|
289
|
+
this.off("mouseup", this.select._endSelect);
|
|
290
|
+
this.off("mousemove", this.select._drawRect);
|
|
291
|
+
|
|
292
|
+
const point2 = event.latlng;
|
|
293
|
+
const point1 = this.select._point1;
|
|
294
|
+
logger.trace("point1", point1);
|
|
295
|
+
logger.trace("point2", point2);
|
|
296
|
+
|
|
297
|
+
this.select._point1 = undefined;
|
|
298
|
+
if (point2.lat === point1.lat && point2.lng === point1.lng) {
|
|
299
|
+
return;
|
|
300
|
+
}
|
|
301
|
+
const area = this.select._get_area(point1, point2);
|
|
302
|
+
this.select._add_query_button(area);
|
|
303
|
+
},
|
|
304
|
+
|
|
305
|
+
/**
|
|
306
|
+
* Add a `query` button to the map (showing a loupe icon) which
|
|
307
|
+
* opens the query panel with a pre-filled query.
|
|
308
|
+
*
|
|
309
|
+
* The generated query searches for entities inside the selected
|
|
310
|
+
* area `a`.
|
|
311
|
+
*
|
|
312
|
+
* A pre-existing query button is removed and the new query button
|
|
313
|
+
* is stored into this._query_button for later references.
|
|
314
|
+
*
|
|
315
|
+
* @param {L.LatLngBounds} a - the selected area.
|
|
316
|
+
* @return {L.Control} the new query button.
|
|
317
|
+
*/
|
|
318
|
+
_add_query_button: function (a) {
|
|
319
|
+
logger.trace("_add_query_button", a, this);
|
|
320
|
+
|
|
321
|
+
// remove older query button
|
|
322
|
+
this._remove_query_button();
|
|
323
|
+
|
|
324
|
+
const north = this._round(a.getNorth());
|
|
325
|
+
const south = this._round(a.getSouth());
|
|
326
|
+
const east = this._round(a.getEast());
|
|
327
|
+
const west = this._round(a.getWest());
|
|
328
|
+
const query = this.generate_query_from_bounds(north, south, west, east);
|
|
329
|
+
|
|
330
|
+
// generate a call-back which opens the query panel with the
|
|
331
|
+
// generated query
|
|
332
|
+
const callback = (event) => {
|
|
333
|
+
logger.trace("click query_button", query, this);
|
|
334
|
+
event.stopPropagation();
|
|
335
|
+
this._query_callback(query);
|
|
336
|
+
};
|
|
337
|
+
|
|
338
|
+
this._query_button = this._get_query_button(callback);
|
|
339
|
+
this._map.addControl(this._query_button);
|
|
340
|
+
return this._query_button;
|
|
341
|
+
},
|
|
342
|
+
|
|
343
|
+
/**
|
|
344
|
+
* Remove a `query` button if present.
|
|
345
|
+
*
|
|
346
|
+
* @return {L.Control} the old query button if present, `undefined`
|
|
347
|
+
* otherwise.
|
|
348
|
+
*/
|
|
349
|
+
_remove_query_button: function () {
|
|
350
|
+
const old = this._query_button;
|
|
351
|
+
if (old) {
|
|
352
|
+
old.remove();
|
|
353
|
+
this._query_button = undefined;
|
|
354
|
+
return old;
|
|
355
|
+
}
|
|
356
|
+
},
|
|
357
|
+
|
|
358
|
+
/**
|
|
359
|
+
* Return the area specified by two coordinates.
|
|
360
|
+
*
|
|
361
|
+
* The edges are parallel to the latitude and longitude of the two
|
|
362
|
+
* points.
|
|
363
|
+
*
|
|
364
|
+
* @param {L.LatLng} point1
|
|
365
|
+
* @param {L.LatLng} point2
|
|
366
|
+
* @returns {L.LatLngBounds} the area.
|
|
367
|
+
*/
|
|
368
|
+
_get_area: function (point1, point2) {
|
|
369
|
+
return L.latLngBounds(point1, point2);
|
|
370
|
+
},
|
|
371
|
+
|
|
372
|
+
/**
|
|
373
|
+
* Round the double value to 3 decimal places.
|
|
374
|
+
*
|
|
375
|
+
* Note: This function is used to round map coordinates to
|
|
376
|
+
* meaningful values.
|
|
377
|
+
*
|
|
378
|
+
* @param {number} d - a double value
|
|
379
|
+
* @returns {number} a rounded double value.
|
|
380
|
+
*/
|
|
381
|
+
_round: function (d) {
|
|
382
|
+
return Math.round(d * 1000) / 1000;
|
|
383
|
+
},
|
|
384
|
+
|
|
385
|
+
/**
|
|
386
|
+
* The first point of the selected area.
|
|
387
|
+
*
|
|
388
|
+
* It is stored by the _startSelect method is used by subsequent
|
|
389
|
+
* execution of the _drawRect method and eventually the _endSelect
|
|
390
|
+
* method.
|
|
391
|
+
*/
|
|
392
|
+
_point1: undefined,
|
|
393
|
+
|
|
394
|
+
/**
|
|
395
|
+
* _select_mode_on indicates whether clicks on the map without
|
|
396
|
+
* pressing shift will start/end/reset selection.
|
|
397
|
+
*/
|
|
398
|
+
_select_mode_on: false,
|
|
399
|
+
|
|
400
|
+
_datamodel: undefined,
|
|
401
|
+
|
|
402
|
+
/**
|
|
403
|
+
* Generate a query to search inside a map area bounded by maximum and
|
|
404
|
+
* minimum latitudes and longitudes.
|
|
405
|
+
*
|
|
406
|
+
* This function uses the configuration from {@link SelectConfig} for
|
|
407
|
+
* the role and entity, the configuration from {@link DataModelConfig}
|
|
408
|
+
* for the latitude and longitude properties, and constructs a query
|
|
409
|
+
* filter from the rectangular bounding box such that all entities with
|
|
410
|
+
* coordinates inside that area are returned.
|
|
411
|
+
*
|
|
412
|
+
* The area is specified as follows:
|
|
413
|
+
*
|
|
414
|
+
* <code>
|
|
415
|
+
* north
|
|
416
|
+
* +-----------------+
|
|
417
|
+
* | |
|
|
418
|
+
* | |
|
|
419
|
+
* west| |east
|
|
420
|
+
* | |
|
|
421
|
+
* | |
|
|
422
|
+
* +-----------------+
|
|
423
|
+
* south
|
|
424
|
+
* </code>
|
|
425
|
+
*
|
|
426
|
+
* The horizontal lines (-) mark the maximum and minimum latitude and
|
|
427
|
+
* the vertical lines (|) mark the maximum and minimum longitude of the
|
|
428
|
+
* area to be searched in.
|
|
429
|
+
*
|
|
430
|
+
* @param {number} north
|
|
431
|
+
* @param {number} south
|
|
432
|
+
* @param {number} west
|
|
433
|
+
* @param {number} east
|
|
434
|
+
* @return {string} a query string.
|
|
435
|
+
*/
|
|
436
|
+
generate_query_from_bounds: function (north, south, west, east) {
|
|
437
|
+
const role = this._datamodel.role;
|
|
438
|
+
var entity = this._datamodel.entity;
|
|
439
|
+
const lat = this._datamodel.lat;
|
|
440
|
+
const lng = this._datamodel.lng;
|
|
441
|
+
|
|
442
|
+
let path = this._current_path;
|
|
443
|
+
if (path && path.length > 0 && entity == "") {
|
|
444
|
+
entity = path[0];
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
var additional_path = "";
|
|
448
|
+
if (path && path.length > 1) {
|
|
449
|
+
additional_path = get_with_POV(path.slice(1, path.length));
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
const query_filter =
|
|
453
|
+
" ( " +
|
|
454
|
+
lat +
|
|
455
|
+
" < '" +
|
|
456
|
+
north +
|
|
457
|
+
"' AND " +
|
|
458
|
+
lat +
|
|
459
|
+
" > '" +
|
|
460
|
+
south +
|
|
461
|
+
"' AND " +
|
|
462
|
+
lng +
|
|
463
|
+
" > '" +
|
|
464
|
+
west +
|
|
465
|
+
"' AND " +
|
|
466
|
+
lng +
|
|
467
|
+
" < '" +
|
|
468
|
+
east +
|
|
469
|
+
"' ) ";
|
|
470
|
+
|
|
471
|
+
const query =
|
|
472
|
+
"FIND " + role + " " + entity + additional_path + " WITH " + query_filter;
|
|
473
|
+
return query;
|
|
474
|
+
},
|
|
475
|
+
|
|
476
|
+
_query_callback: (query) => {
|
|
477
|
+
logger.error("Unconfigured queryCallback", query);
|
|
478
|
+
},
|
|
479
|
+
};
|
|
480
|
+
|
|
481
|
+
export function SearchControl({ currentPath, queryCallback }) {
|
|
482
|
+
const {
|
|
483
|
+
config: { datamodel },
|
|
484
|
+
} = useConfig();
|
|
485
|
+
const map = useMap();
|
|
486
|
+
|
|
487
|
+
if (!map.select) {
|
|
488
|
+
map.addHandler("select", L.Handler.extend(select_handler));
|
|
489
|
+
map.select.enable();
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
map.select._datamodel = datamodel;
|
|
493
|
+
map.select._query_callback = queryCallback;
|
|
494
|
+
map.select._current_path = currentPath;
|
|
495
|
+
|
|
496
|
+
return null;
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
SearchControl.propTypes = {
|
|
500
|
+
currentPath: PropTypes.arrayOf(PropTypes.string),
|
|
501
|
+
queryCallback: PropTypes.func,
|
|
502
|
+
};
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
import React, { useState, useCallback, useEffect } from "react";
|
|
2
|
+
import PropTypes from "prop-types";
|
|
3
|
+
import { logger } from "../logging";
|
|
4
|
+
import { Await } from "@indiscale/linkahead-webui-core-components";
|
|
5
|
+
import { get_map_config, get_local_config } from "../MapConfig";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* ON: map is visible, toggle button click will be processed
|
|
9
|
+
* OFF: map is not visible, toggle button click will be processed
|
|
10
|
+
* SWITCH_ON: map is about to be shown, clicks will be ignored
|
|
11
|
+
* SWITCH_OFF: map is about to be hidden, clicks will be ignored
|
|
12
|
+
*/
|
|
13
|
+
const STATE = {
|
|
14
|
+
ON: "on",
|
|
15
|
+
OFF: "off",
|
|
16
|
+
SWITCH_ON: "switch_on",
|
|
17
|
+
SWITCH_OFF: "switch_off",
|
|
18
|
+
ERROR: "error",
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
const get_initial_state = (isOn) => {
|
|
22
|
+
if (typeof isOn === "undefined") {
|
|
23
|
+
isOn = get_local_config()["show"];
|
|
24
|
+
}
|
|
25
|
+
if (isOn) {
|
|
26
|
+
return STATE.SWITCH_ON;
|
|
27
|
+
}
|
|
28
|
+
return STATE.SWITCH_OFF;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const store_session_state = (state) => {
|
|
32
|
+
const isOn = state === STATE.ON;
|
|
33
|
+
sessionStorage.setItem("caosdb_map.show", JSON.stringify(isOn));
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const toggle = (container, state, delay) => {
|
|
37
|
+
if (state !== STATE.SWITCH_OFF && state !== STATE.SWITCH_ON) {
|
|
38
|
+
throw new Error(`Illegal state transition from current state '${state}'`);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const containerRef = document.querySelector(container);
|
|
42
|
+
if (!containerRef) throw new Error(`Map container not found: ${container}.`);
|
|
43
|
+
|
|
44
|
+
return new Promise((resolve, reject) => {
|
|
45
|
+
try {
|
|
46
|
+
// TODO animate transition
|
|
47
|
+
let nextState = STATE.OFF;
|
|
48
|
+
if (state === STATE.SWITCH_ON) {
|
|
49
|
+
nextState = STATE.ON;
|
|
50
|
+
containerRef.classList.remove("d-none");
|
|
51
|
+
} else {
|
|
52
|
+
containerRef.classList.add("d-none");
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
containerRef.dispatchEvent(
|
|
56
|
+
new Event("caosdb-webui-ext-map.after-toggle")
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
setTimeout(() => {
|
|
60
|
+
resolve(nextState);
|
|
61
|
+
}, delay);
|
|
62
|
+
} catch (err) {
|
|
63
|
+
reject(err);
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
const _ToggleMapButton = ({
|
|
69
|
+
tag,
|
|
70
|
+
showInitial,
|
|
71
|
+
className,
|
|
72
|
+
mapContainer,
|
|
73
|
+
labelOn,
|
|
74
|
+
labelOff,
|
|
75
|
+
delay,
|
|
76
|
+
titleOn,
|
|
77
|
+
titleOff,
|
|
78
|
+
config = {},
|
|
79
|
+
}) => {
|
|
80
|
+
// We want that if show is present in the config, it overrides the
|
|
81
|
+
// showInitial prop and shows the map on load.
|
|
82
|
+
const [state, setState] = useState(
|
|
83
|
+
get_initial_state(config.show || showInitial)
|
|
84
|
+
);
|
|
85
|
+
|
|
86
|
+
const callback = useCallback(() => {
|
|
87
|
+
setState((prevState) => {
|
|
88
|
+
if (prevState === STATE.ON) {
|
|
89
|
+
logger.trace("setState", STATE.SWITCH_OFF);
|
|
90
|
+
store_session_state(STATE.OFF);
|
|
91
|
+
return STATE.SWITCH_OFF;
|
|
92
|
+
} else if (prevState === STATE.OFF) {
|
|
93
|
+
if (
|
|
94
|
+
!document.querySelector(mapContainer) ||
|
|
95
|
+
document.querySelector(mapContainer).children.length <= 1
|
|
96
|
+
) {
|
|
97
|
+
// If the map container is not found or has only one child,
|
|
98
|
+
// we trigger a reload event to re-render the map as this means Leaflet is not loaded
|
|
99
|
+
logger.trace(`Map container not found or only one child.`);
|
|
100
|
+
document.dispatchEvent(new Event("map:reload"));
|
|
101
|
+
}
|
|
102
|
+
logger.trace("setState", STATE.SWITCH_ON);
|
|
103
|
+
store_session_state(STATE.ON);
|
|
104
|
+
return STATE.SWITCH_ON;
|
|
105
|
+
}
|
|
106
|
+
});
|
|
107
|
+
// else: no-op
|
|
108
|
+
}, [mapContainer]);
|
|
109
|
+
|
|
110
|
+
useEffect(() => {
|
|
111
|
+
if (state === STATE.SWITCH_ON || state === STATE.SWITCH_OFF) {
|
|
112
|
+
toggle(mapContainer, state, delay).then(
|
|
113
|
+
(nextState) => {
|
|
114
|
+
logger.trace("setState", nextState);
|
|
115
|
+
setState(nextState);
|
|
116
|
+
},
|
|
117
|
+
(err) => {
|
|
118
|
+
logger.error(err);
|
|
119
|
+
setState(STATE.ERROR);
|
|
120
|
+
}
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
// else: no-op
|
|
124
|
+
}, [state, setState, mapContainer, delay]);
|
|
125
|
+
|
|
126
|
+
if (state === STATE.ERROR) {
|
|
127
|
+
return <span>{"ERROR"}</span>;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const label =
|
|
131
|
+
state === STATE.ON || state === STATE.SWITCH_ON ? labelOn : labelOff;
|
|
132
|
+
|
|
133
|
+
const props = {
|
|
134
|
+
title: state === STATE.ON || state === STATE.SWITCH_ON ? titleOn : titleOff,
|
|
135
|
+
role: "button",
|
|
136
|
+
href: "#",
|
|
137
|
+
onClick: callback,
|
|
138
|
+
};
|
|
139
|
+
if (className) {
|
|
140
|
+
props["className"] = className;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const result = React.createElement(tag, props, <span>{label}</span>);
|
|
144
|
+
return result;
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
_ToggleMapButton.propTypes = {
|
|
148
|
+
tag: PropTypes.oneOf(["a", "button"]),
|
|
149
|
+
showInitial: PropTypes.bool,
|
|
150
|
+
className: PropTypes.string,
|
|
151
|
+
mapContainer: PropTypes.string,
|
|
152
|
+
labelOn: PropTypes.string,
|
|
153
|
+
labelOff: PropTypes.string,
|
|
154
|
+
delay: PropTypes.number,
|
|
155
|
+
titleOn: PropTypes.string,
|
|
156
|
+
titleOff: PropTypes.string,
|
|
157
|
+
config: PropTypes.object,
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
const ToggleMapButton = (props) => (
|
|
161
|
+
<Await
|
|
162
|
+
promise={get_map_config()}
|
|
163
|
+
then={(config) => {
|
|
164
|
+
return config.disabled || <_ToggleMapButton {...props} config={config} />;
|
|
165
|
+
}}
|
|
166
|
+
loading={<span />}
|
|
167
|
+
/>
|
|
168
|
+
);
|
|
169
|
+
|
|
170
|
+
ToggleMapButton.propTypes = {
|
|
171
|
+
tag: PropTypes.oneOf(["a", "button"]),
|
|
172
|
+
className: PropTypes.string,
|
|
173
|
+
mapContainer: PropTypes.string,
|
|
174
|
+
labelOn: PropTypes.string,
|
|
175
|
+
labelOff: PropTypes.string,
|
|
176
|
+
titleOn: PropTypes.string,
|
|
177
|
+
titleOff: PropTypes.string,
|
|
178
|
+
delay: PropTypes.number,
|
|
179
|
+
showInitial: PropTypes.bool,
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
ToggleMapButton.defaultProps = {
|
|
183
|
+
tag: "a",
|
|
184
|
+
className: undefined,
|
|
185
|
+
mapContainer: ".caosdb-f-map-panel",
|
|
186
|
+
labelOn: "Hide Map",
|
|
187
|
+
labelOff: "Show Map",
|
|
188
|
+
titleOn: "Click to hide the map.",
|
|
189
|
+
titleOff: "Click to show the map.",
|
|
190
|
+
delay: 500,
|
|
191
|
+
showInitial: undefined,
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
export { ToggleMapButton };
|