@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,536 @@
|
|
|
1
|
+
import { useState } from "react";
|
|
2
|
+
import PropTypes from "prop-types";
|
|
3
|
+
import L, { DivIcon } from "leaflet";
|
|
4
|
+
import { Marker, Popup } from "react-leaflet";
|
|
5
|
+
import { logger } from "./logging";
|
|
6
|
+
import {
|
|
7
|
+
Property,
|
|
8
|
+
TransactionService,
|
|
9
|
+
} from "@indiscale/linkahead-webui-entity-service";
|
|
10
|
+
import { useConfig } from "./context/ConfigProvider";
|
|
11
|
+
import { usePathId } from "./context/PathIdProvider";
|
|
12
|
+
import { get } from "lodash";
|
|
13
|
+
import { DEFAULT_IFRAMESETTINGS_PATHID } from "./constants";
|
|
14
|
+
import "./Map.helpers.css";
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Return an array of components displaying a badge with the parents of an entity.
|
|
18
|
+
*
|
|
19
|
+
* @param {string[]} parents - the parent's names.
|
|
20
|
+
* @returns array of components
|
|
21
|
+
*/
|
|
22
|
+
const make_parent_labels = function (parents) {
|
|
23
|
+
logger.trace("make_parent_labels", parents);
|
|
24
|
+
if (!parents || !Array.isArray(parents) || parents.length === 0) {
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
return (
|
|
28
|
+
<>
|
|
29
|
+
{parents.map((par, index) => (
|
|
30
|
+
<span className="badge caosdb-f-map-parent-badge" key={index}>
|
|
31
|
+
{par}
|
|
32
|
+
</span>
|
|
33
|
+
))}
|
|
34
|
+
</>
|
|
35
|
+
);
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Generates a Property Operator Value (POV) expression by chaining the
|
|
40
|
+
* provided arguments with "WITH".
|
|
41
|
+
*
|
|
42
|
+
* @param {string[]} path - array with the names of RecordTypes
|
|
43
|
+
* @returns {string} string with the the filter
|
|
44
|
+
*/
|
|
45
|
+
const get_with_POV = function (path) {
|
|
46
|
+
var pov = "";
|
|
47
|
+
for (let p of path) {
|
|
48
|
+
pov = pov + ` WITH "${p}" `;
|
|
49
|
+
}
|
|
50
|
+
return pov;
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Generates a Property Operator Value (POV) by joining ids with OR.
|
|
55
|
+
*
|
|
56
|
+
* @param {number[]} ids - array of ids for the filter
|
|
57
|
+
* @returns {string} string with the the filter
|
|
58
|
+
*/
|
|
59
|
+
const get_id_POV = function (ids) {
|
|
60
|
+
ids = ids.map((x) => "id=" + x);
|
|
61
|
+
return "WITH " + ids.join(" or ");
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
const get_selector = function (path) {
|
|
65
|
+
const sliced = path.slice(1, path.length);
|
|
66
|
+
var selector = sliced.join(".");
|
|
67
|
+
if (selector != "") {
|
|
68
|
+
selector = selector + ".";
|
|
69
|
+
}
|
|
70
|
+
return selector;
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Generates a SELECT query string that applies the provided path of
|
|
75
|
+
* properties as POV and as selector
|
|
76
|
+
*
|
|
77
|
+
* If ids is provided, the condition is not created from the path, but
|
|
78
|
+
* from ids.
|
|
79
|
+
*
|
|
80
|
+
* @param {DataModelConfig} datamodel - datamodel of the entities to be returned.
|
|
81
|
+
* @param {string[]} path - array with the names of RecordTypes
|
|
82
|
+
* @param {number[]} ids - array of ids for the filter
|
|
83
|
+
* @returns {string} query string
|
|
84
|
+
*/
|
|
85
|
+
const get_select_with_path = function (datamodel, path, ids) {
|
|
86
|
+
if (typeof datamodel === "undefined") {
|
|
87
|
+
throw new Error("Supply the datamodel.");
|
|
88
|
+
}
|
|
89
|
+
if (typeof path === "undefined" || path.length == 0) {
|
|
90
|
+
throw new Error("Supply at least a RecordType.");
|
|
91
|
+
}
|
|
92
|
+
const recordtype = path[0];
|
|
93
|
+
const selector = get_selector(path);
|
|
94
|
+
var pov = undefined;
|
|
95
|
+
if (typeof ids === "undefined") {
|
|
96
|
+
const sliced = path.slice(1, path.length);
|
|
97
|
+
pov =
|
|
98
|
+
get_with_POV(sliced) +
|
|
99
|
+
` WITH ( "${datamodel.lat}" AND "${datamodel.lng}" )`;
|
|
100
|
+
} else {
|
|
101
|
+
pov = get_id_POV(ids);
|
|
102
|
+
}
|
|
103
|
+
return `SELECT id,name,parent,${selector}${datamodel.lat},${selector}${datamodel.lng} FROM ENTITY "${recordtype}" ${pov} `;
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
const LINE_BREAK_HTML = '<div class="linebreak"></div>';
|
|
107
|
+
|
|
108
|
+
export function renderHtmlTemplate({ format }, result, { missing = "" } = {}) {
|
|
109
|
+
return (
|
|
110
|
+
String(format)
|
|
111
|
+
// 1) replace <br></br> (and whitespace between)
|
|
112
|
+
.replace(/<\s*br\s*>\s*<\s*\/\s*br\s*>/gi, LINE_BREAK_HTML)
|
|
113
|
+
// 2) replace <br>, <br/>, <br />
|
|
114
|
+
.replace(/<\s*br\s*\/?\s*>/gi, LINE_BREAK_HTML)
|
|
115
|
+
// 3) replace {placeholders}
|
|
116
|
+
.replace(/\{([^}]+)\}/g, (_, rawKey) => {
|
|
117
|
+
const key = rawKey.trim();
|
|
118
|
+
const value = result?.[key];
|
|
119
|
+
return value == null ? missing : String(value);
|
|
120
|
+
})
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Component which shows a customentity containing info and links to the entity on the
|
|
125
|
+
* map.
|
|
126
|
+
*/
|
|
127
|
+
function EntityPopup({ lat, lng, entity, path, customContent }) {
|
|
128
|
+
const { pathId } = usePathId();
|
|
129
|
+
|
|
130
|
+
const dms_lat = L.NumberFormatter.toDMS(lat).replace("°", "°");
|
|
131
|
+
const dms_lng = L.NumberFormatter.toDMS(lng).replace("°", "°");
|
|
132
|
+
|
|
133
|
+
let extra_loc_hint = "";
|
|
134
|
+
if (path && path.length > 1) {
|
|
135
|
+
extra_loc_hint = (
|
|
136
|
+
<div>{`Location of related ${path[path.length - 1]}`}</div>
|
|
137
|
+
);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const parent_labels = make_parent_labels(entity.parents);
|
|
141
|
+
const name_label = make_entity_name_label(entity.id, entity.name);
|
|
142
|
+
return (
|
|
143
|
+
<Popup key={`${entity.id}-${pathId}`}>
|
|
144
|
+
{parent_labels}
|
|
145
|
+
{name_label}
|
|
146
|
+
<div className="small text-muted">
|
|
147
|
+
{extra_loc_hint}
|
|
148
|
+
{`Lat: ${dms_lat} Lng: ${dms_lng}`}
|
|
149
|
+
</div>
|
|
150
|
+
{customContent ? (
|
|
151
|
+
<div
|
|
152
|
+
style={{ display: "flex", flexDirection: "column" }}
|
|
153
|
+
className="customEntityPreview small text-muted"
|
|
154
|
+
dangerouslySetInnerHTML={{ __html: customContent }}
|
|
155
|
+
/>
|
|
156
|
+
) : null}
|
|
157
|
+
</Popup>
|
|
158
|
+
);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
EntityPopup.propTypes = {
|
|
162
|
+
path: PropTypes.arrayOf(PropTypes.string),
|
|
163
|
+
customContent: PropTypes.string,
|
|
164
|
+
entity: PropTypes.shape({
|
|
165
|
+
id: PropTypes.string,
|
|
166
|
+
name: PropTypes.string,
|
|
167
|
+
parents: PropTypes.arrayOf(PropTypes.string),
|
|
168
|
+
customEntityPreview: PropTypes.object,
|
|
169
|
+
}),
|
|
170
|
+
lat: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
|
|
171
|
+
lng: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
const getCustomEntityContent = async ({
|
|
175
|
+
config,
|
|
176
|
+
saveContent,
|
|
177
|
+
entityId,
|
|
178
|
+
pathId,
|
|
179
|
+
}) => {
|
|
180
|
+
const service = get_transaction_service();
|
|
181
|
+
// For custom Entity Preview
|
|
182
|
+
// Check that in the JSON Config there is an entry that correspondes to
|
|
183
|
+
// the current path and get the entryname
|
|
184
|
+
const configKey = Object.keys(config?.entityPreview ?? {}).find(
|
|
185
|
+
(entityPreviewItem) => entityPreviewItem === pathId
|
|
186
|
+
);
|
|
187
|
+
|
|
188
|
+
// With that entryname get the settings from within
|
|
189
|
+
const entityPreviewSettings = get(config?.entityPreview, configKey) || {};
|
|
190
|
+
const queryString =
|
|
191
|
+
entityPreviewSettings?.query &&
|
|
192
|
+
entityPreviewSettings.query.replace("{id}", String(entityId));
|
|
193
|
+
|
|
194
|
+
// if no customcontent for current path or lacking querystring
|
|
195
|
+
// eject !
|
|
196
|
+
if (!configKey || !queryString) {
|
|
197
|
+
saveContent(null);
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
try {
|
|
202
|
+
const queryResultRes = await service.executeQuery(queryString);
|
|
203
|
+
const queryResult = await get_select_results(
|
|
204
|
+
queryResultRes
|
|
205
|
+
.getResponsesList()[0]
|
|
206
|
+
.getRetrieveResponse()
|
|
207
|
+
.getSelectResult()
|
|
208
|
+
);
|
|
209
|
+
|
|
210
|
+
const entityPreview = renderHtmlTemplate(
|
|
211
|
+
entityPreviewSettings,
|
|
212
|
+
queryResult[0]
|
|
213
|
+
);
|
|
214
|
+
|
|
215
|
+
saveContent(entityPreview);
|
|
216
|
+
} catch (err) {
|
|
217
|
+
/* eslint-disable-line no-empty */
|
|
218
|
+
throw Error(
|
|
219
|
+
`There was an issue executing the custom query: ${queryString}`,
|
|
220
|
+
err
|
|
221
|
+
);
|
|
222
|
+
}
|
|
223
|
+
};
|
|
224
|
+
|
|
225
|
+
function get_transaction_service() {
|
|
226
|
+
const api = process?.env?.GRPC_API_URI || undefined;
|
|
227
|
+
return new TransactionService(api);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// TODO this should be moved to the legacy-adapter
|
|
231
|
+
function getBasePath() {
|
|
232
|
+
if (window.connection?.getBasePath) {
|
|
233
|
+
return window.connection.getBasePath();
|
|
234
|
+
}
|
|
235
|
+
return "/";
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* Create a div component which shows the name of the entity and contains a
|
|
240
|
+
* link which points to the entity.
|
|
241
|
+
*
|
|
242
|
+
* This is shown as a part of the pop-up when the user click on an
|
|
243
|
+
* entity marker in the map.
|
|
244
|
+
*/
|
|
245
|
+
const make_entity_name_label = function (id, name) {
|
|
246
|
+
const entity_on_page = !!document.getElementById(id);
|
|
247
|
+
const href = entity_on_page ? `#${id}` : getBasePath() + `Entity/${id}`;
|
|
248
|
+
const target = entity_on_page ? "_self" : "_blank";
|
|
249
|
+
const title = entity_on_page
|
|
250
|
+
? "Jump to this entity."
|
|
251
|
+
: "Browse to this entity.";
|
|
252
|
+
return (
|
|
253
|
+
<div className="caosdb-f-map-entity-name-label">
|
|
254
|
+
{name}
|
|
255
|
+
<a
|
|
256
|
+
href={href}
|
|
257
|
+
title={title}
|
|
258
|
+
target={target}
|
|
259
|
+
className="caosdb-f-map-popup-entity-link"
|
|
260
|
+
>
|
|
261
|
+
<i className="bi bi-box-arrow-up-right" />
|
|
262
|
+
</a>
|
|
263
|
+
</div>
|
|
264
|
+
);
|
|
265
|
+
};
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* Return the html string for one option in the layer-chooser menu.
|
|
269
|
+
*/
|
|
270
|
+
function make_layer_chooser_html(icon, name, description) {
|
|
271
|
+
return `<span title="${description}">${icon} ${name}</span>`;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
/**
|
|
275
|
+
* Single marker component
|
|
276
|
+
*/
|
|
277
|
+
function EntityMarker({ icon_options, zIndexOffset, lat, lng, path, entity }) {
|
|
278
|
+
// Get current Path from provider
|
|
279
|
+
const { pathId } = usePathId();
|
|
280
|
+
// and config
|
|
281
|
+
const { config } = useConfig();
|
|
282
|
+
const [content, saveContent] = useState();
|
|
283
|
+
const configKey = Object.keys(config?.entityPreview ?? {})[0];
|
|
284
|
+
|
|
285
|
+
if (!icon_options.className) {
|
|
286
|
+
icon_options.className = "";
|
|
287
|
+
}
|
|
288
|
+
const isStandalone = process.env.STANDALONE_MODE === true;
|
|
289
|
+
const standaloneEntityPreviewPath =
|
|
290
|
+
config?.iframeSettings?.entityPreviewPath || DEFAULT_IFRAMESETTINGS_PATHID;
|
|
291
|
+
|
|
292
|
+
const icon = new DivIcon(icon_options);
|
|
293
|
+
return (
|
|
294
|
+
<Marker
|
|
295
|
+
icon={icon}
|
|
296
|
+
zIndexOffset={zIndexOffset}
|
|
297
|
+
position={[lat, lng]}
|
|
298
|
+
eventHandlers={{
|
|
299
|
+
// Fetch custom content (if existing) for popup
|
|
300
|
+
// upon click event on marker
|
|
301
|
+
click: () => {
|
|
302
|
+
// When in standalone mode no path dropdown available so we need a pathId whic
|
|
303
|
+
// is provided through the configs
|
|
304
|
+
const globalPathId = isStandalone
|
|
305
|
+
? standaloneEntityPreviewPath
|
|
306
|
+
: pathId;
|
|
307
|
+
if (configKey === globalPathId) {
|
|
308
|
+
getCustomEntityContent({
|
|
309
|
+
config,
|
|
310
|
+
saveContent,
|
|
311
|
+
entityId: entity.id,
|
|
312
|
+
pathId: globalPathId,
|
|
313
|
+
});
|
|
314
|
+
} else {
|
|
315
|
+
saveContent(null);
|
|
316
|
+
}
|
|
317
|
+
},
|
|
318
|
+
}}
|
|
319
|
+
>
|
|
320
|
+
<EntityPopup
|
|
321
|
+
lat={lat}
|
|
322
|
+
lng={lng}
|
|
323
|
+
path={path}
|
|
324
|
+
entity={entity}
|
|
325
|
+
pathId={pathId}
|
|
326
|
+
customContent={content}
|
|
327
|
+
/>
|
|
328
|
+
</Marker>
|
|
329
|
+
);
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
EntityMarker.propTypes = {
|
|
333
|
+
path: PropTypes.arrayOf(PropTypes.string),
|
|
334
|
+
entity: PropTypes.shape({
|
|
335
|
+
id: PropTypes.string,
|
|
336
|
+
name: PropTypes.string,
|
|
337
|
+
parents: PropTypes.arrayOf(PropTypes.string),
|
|
338
|
+
}),
|
|
339
|
+
lat: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
|
|
340
|
+
lng: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
|
|
341
|
+
icon_options: PropTypes.object,
|
|
342
|
+
zIndexOffset: PropTypes.number,
|
|
343
|
+
};
|
|
344
|
+
|
|
345
|
+
function isUndefinedOrEmpty(obj) {
|
|
346
|
+
return typeof obj === "undefined" || (obj.length && obj.length == 0);
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
/**
|
|
350
|
+
* Return a component for markers on the map for an array of entities.
|
|
351
|
+
*/
|
|
352
|
+
function EntityMarkers({ path, entities, zIndexOffset, icon_options }) {
|
|
353
|
+
var ret = [];
|
|
354
|
+
for (const entity of entities) {
|
|
355
|
+
var lat_vals = entity.lat;
|
|
356
|
+
var lng_vals = entity.lng;
|
|
357
|
+
|
|
358
|
+
if (isUndefinedOrEmpty(lng_vals) || isUndefinedOrEmpty(lat_vals)) {
|
|
359
|
+
logger.debug(
|
|
360
|
+
"undefined latitude or longitude",
|
|
361
|
+
entity,
|
|
362
|
+
lat_vals,
|
|
363
|
+
lng_vals
|
|
364
|
+
);
|
|
365
|
+
continue;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
// we need lat_vals and lng_lavs to be arrays so we make them
|
|
369
|
+
// be one
|
|
370
|
+
var is_list_lat = true;
|
|
371
|
+
if (!Array.isArray(lat_vals)) {
|
|
372
|
+
lat_vals = [lat_vals];
|
|
373
|
+
is_list_lat = false;
|
|
374
|
+
}
|
|
375
|
+
var is_list_lng = true;
|
|
376
|
+
if (!Array.isArray(lng_vals)) {
|
|
377
|
+
lng_vals = [lng_vals];
|
|
378
|
+
is_list_lng = false;
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
// both array's length must match
|
|
382
|
+
if (
|
|
383
|
+
is_list_lng !== is_list_lat ||
|
|
384
|
+
(is_list_lat && is_list_lng && lat_vals.length !== lng_vals.length)
|
|
385
|
+
) {
|
|
386
|
+
logger.error(
|
|
387
|
+
"Cannot show this entity on the map. " +
|
|
388
|
+
"Its lat/long properties have different lenghts: ",
|
|
389
|
+
entity
|
|
390
|
+
);
|
|
391
|
+
continue;
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
// zip both arrays
|
|
395
|
+
// [lat1, lat2, ... latN]
|
|
396
|
+
// [lng1, lng2, ... lngN]
|
|
397
|
+
// into one
|
|
398
|
+
// [[lat1,lng1],[lat2,lng2],... [latN,lngN]]
|
|
399
|
+
const const_lng_vals_array = lng_vals;
|
|
400
|
+
var latlngs = lat_vals.map(function (e, i) {
|
|
401
|
+
return [e, const_lng_vals_array[i]];
|
|
402
|
+
});
|
|
403
|
+
|
|
404
|
+
logger.debug(
|
|
405
|
+
`create point marker(s) at ${latlngs} for`,
|
|
406
|
+
entity,
|
|
407
|
+
zIndexOffset,
|
|
408
|
+
icon_options
|
|
409
|
+
);
|
|
410
|
+
for (let latlng of latlngs) {
|
|
411
|
+
ret.push(
|
|
412
|
+
<EntityMarker
|
|
413
|
+
key={`${entity.id}-${latlng[0]}-${latlng[1]}-${ret.length}`}
|
|
414
|
+
icon_options={icon_options}
|
|
415
|
+
zIndexOffset={zIndexOffset}
|
|
416
|
+
lat={latlng[0]}
|
|
417
|
+
lng={latlng[1]}
|
|
418
|
+
path={path}
|
|
419
|
+
entity={entity}
|
|
420
|
+
/>
|
|
421
|
+
);
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
/* Code for showing a PATH on the map.
|
|
425
|
+
* Maybe we re-use it later
|
|
426
|
+
*
|
|
427
|
+
logger.debug(`create path line at ${latlngs} for`,
|
|
428
|
+
map_entity);
|
|
429
|
+
|
|
430
|
+
var opts = {color:'red', smoothFactor: 10.0, weight: 1.5, opacity: 0.5};
|
|
431
|
+
var opts_2 = {color:'green', smoothFactor: 10.0, weight: 3, opacity: 0.5};
|
|
432
|
+
var path = L.polyline(latlngs, opts);
|
|
433
|
+
if (make_popup) {
|
|
434
|
+
path.bindPopup(make_popup(map_entity, datamodel, lat, lng));
|
|
435
|
+
}
|
|
436
|
+
path.on("mouseover",()=>path.setStyle(opts_2));
|
|
437
|
+
path.on("mouseout",()=>path.setStyle(opts));
|
|
438
|
+
ret.push(path);
|
|
439
|
+
*
|
|
440
|
+
*
|
|
441
|
+
*/
|
|
442
|
+
}
|
|
443
|
+
return <>{ret}</>;
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
EntityMarkers.propTypes = {
|
|
447
|
+
path: PropTypes.arrayOf(PropTypes.string),
|
|
448
|
+
zIndexOffset: PropTypes.number,
|
|
449
|
+
icon_options: PropTypes.object,
|
|
450
|
+
entities: PropTypes.arrayOf(
|
|
451
|
+
PropTypes.shape({
|
|
452
|
+
id: PropTypes.string,
|
|
453
|
+
name: PropTypes.string,
|
|
454
|
+
parents: PropTypes.arrayOf(PropTypes.string),
|
|
455
|
+
lat: PropTypes.oneOfType([
|
|
456
|
+
PropTypes.string,
|
|
457
|
+
PropTypes.number,
|
|
458
|
+
PropTypes.arrayOf(
|
|
459
|
+
PropTypes.oneOfType([PropTypes.string, PropTypes.number])
|
|
460
|
+
),
|
|
461
|
+
]),
|
|
462
|
+
lng: PropTypes.oneOfType([
|
|
463
|
+
PropTypes.string,
|
|
464
|
+
PropTypes.number,
|
|
465
|
+
PropTypes.arrayOf(
|
|
466
|
+
PropTypes.oneOfType([PropTypes.string, PropTypes.number])
|
|
467
|
+
),
|
|
468
|
+
]),
|
|
469
|
+
})
|
|
470
|
+
),
|
|
471
|
+
};
|
|
472
|
+
|
|
473
|
+
/**
|
|
474
|
+
* Return the value of a single cell in a select result as a POJO.
|
|
475
|
+
*
|
|
476
|
+
* TODO move to linkahead-webui-entity-service
|
|
477
|
+
*/
|
|
478
|
+
function parseCell(cell) {
|
|
479
|
+
// evil, refactor in linkahead-webui-entity-service
|
|
480
|
+
const property = new Property({ getValue: () => cell });
|
|
481
|
+
return property.getValue();
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
/**
|
|
485
|
+
* Return the select results as a list of POJOs.
|
|
486
|
+
*
|
|
487
|
+
* TODO move to linkahead-webui-entity-service
|
|
488
|
+
*/
|
|
489
|
+
function get_select_results(select_result) {
|
|
490
|
+
const header = select_result.getHeader();
|
|
491
|
+
if (!header) {
|
|
492
|
+
return [];
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
const data_rows = select_result.getDataRowsList();
|
|
496
|
+
if (!data_rows) {
|
|
497
|
+
return [];
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
const columns = header.getColumnsList().map((col) => col.getName());
|
|
501
|
+
const data = data_rows.map((row) => {
|
|
502
|
+
const obj = {};
|
|
503
|
+
|
|
504
|
+
const cells = row.getCellsList();
|
|
505
|
+
for (let i = 0; i < cells.length; i++) {
|
|
506
|
+
console.assert(
|
|
507
|
+
columns.length === cells.length,
|
|
508
|
+
"broken select result. columns don't match data_rows"
|
|
509
|
+
);
|
|
510
|
+
|
|
511
|
+
const key = columns[i];
|
|
512
|
+
const value = parseCell(cells[i]);
|
|
513
|
+
|
|
514
|
+
obj[key] = value;
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
return obj;
|
|
518
|
+
});
|
|
519
|
+
|
|
520
|
+
logger.trace("get_select_results", columns, data);
|
|
521
|
+
return data;
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
export {
|
|
525
|
+
get_select_results,
|
|
526
|
+
get_transaction_service,
|
|
527
|
+
EntityMarkers,
|
|
528
|
+
EntityMarker,
|
|
529
|
+
get_selector,
|
|
530
|
+
make_entity_name_label,
|
|
531
|
+
make_parent_labels,
|
|
532
|
+
get_select_with_path,
|
|
533
|
+
get_id_POV,
|
|
534
|
+
get_with_POV,
|
|
535
|
+
make_layer_chooser_html,
|
|
536
|
+
};
|