|
12 | 12 | let maps = {}; |
13 | 13 | let locationId = null; |
14 | 14 | let popstateHandler = null; |
| 15 | + let hashchangeHandler = null; |
15 | 16 | const escapeHtml = function (text) { |
16 | 17 | if (!text) return ""; |
17 | 18 | const div = document.createElement("div"); |
|
41 | 42 | } |
42 | 43 | const params = new URLSearchParams(indoorFragment); |
43 | 44 | const fragmentId = params.get("id"); |
44 | | - // fragments format is expected to be "<locationId>:<floor>" |
45 | | - const [fragmentLocationId, fragmentFloor] = fragmentId?.split(":") || []; |
| 45 | + // fragments format is expected to be "<locationId>_<floor>" |
| 46 | + const [fragmentLocationId, fragmentFloor] = fragmentId?.split("_") || []; |
46 | 47 | if (!fragmentLocationId || fragmentFloor == null) { |
47 | 48 | return null; |
48 | 49 | } |
|
55 | 56 | "000", |
56 | 57 | fragmentLocationId, |
57 | 58 | ); |
58 | | - openFloorPlan(`${floorplanUrl}`, fragmentLocationId, fragmentFloor); |
| 59 | + openFloorPlan(floorplanUrl, fragmentLocationId, fragmentFloor); |
59 | 60 | } |
60 | 61 |
|
61 | | - async function openFloorPlan(url, id = null, floor = currentFloor) { |
62 | | - if ($("#floorplan-overlay")) { |
63 | | - closeButtonHandler(); |
64 | | - } |
65 | | - locationId = id; |
66 | | - // Handle browser back/forward navigation: close the indoor map overlay |
67 | | - // If the indoor map fragment is removed from the URL, close the overlay |
68 | | - // should be done before async tasks that are performed later |
| 62 | + // Handle browser back/forward navigation |
| 63 | + function setupPopstateHandler() { |
69 | 64 | if (popstateHandler) { |
70 | 65 | window.removeEventListener("popstate", popstateHandler); |
| 66 | + popstateHandler = null; |
71 | 67 | } |
72 | 68 | popstateHandler = () => { |
73 | 69 | const indoorMapId = getIndoorMapIdFromUrl(); |
|
77 | 73 | } |
78 | 74 | }; |
79 | 75 | window.addEventListener("popstate", popstateHandler); |
| 76 | + } |
| 77 | + |
| 78 | + // Handle manual URL fragment changes (e.g., pasting URL in address bar) |
| 79 | + function setupHashChangeHandler() { |
| 80 | + if (hashchangeHandler) { |
| 81 | + window.removeEventListener("hashchange", hashchangeHandler); |
| 82 | + hashchangeHandler = null; |
| 83 | + } |
| 84 | + hashchangeHandler = () => { |
| 85 | + const indoorMapId = getIndoorMapIdFromUrl(); |
| 86 | + const isOverlayOpen = $("#floorplan-overlay").length > 0; |
| 87 | + if (indoorMapId) { |
| 88 | + const { fragmentLocationId, fragmentFloor } = indoorMapId; |
| 89 | + const floorplanUrl = window._owGeoMapConfig.indoorCoordinatesUrl.replace( |
| 90 | + "000", |
| 91 | + fragmentLocationId, |
| 92 | + ); |
| 93 | + openFloorPlan(floorplanUrl, fragmentLocationId, fragmentFloor); |
| 94 | + } else if (isOverlayOpen) { |
| 95 | + closeButtonHandler(); |
| 96 | + } |
| 97 | + }; |
| 98 | + window.addEventListener("hashchange", hashchangeHandler); |
| 99 | + } |
| 100 | + |
| 101 | + async function openFloorPlan(url, id = null, floor = currentFloor) { |
| 102 | + if ($("#floorplan-overlay").length) { |
| 103 | + closeButtonHandler(); |
| 104 | + } |
| 105 | + // coerce url to string |
| 106 | + url = String(url); |
| 107 | + locationId = id; |
| 108 | + // Handle browser back/forward navigation: close the indoor map overlay |
| 109 | + // If the indoor map fragment is removed from the URL, close the overlay |
| 110 | + // should be done before async tasks that are performed later |
| 111 | + setupPopstateHandler(); |
| 112 | + // Handle manual url changes like pasting new url after the initial load |
| 113 | + setupHashChangeHandler(); |
80 | 114 |
|
81 | 115 | await fetchData(url, floor); |
82 | 116 | const idx = floors.indexOf(currentFloor); |
|
294 | 328 | } |
295 | 329 | $floorDiv.show(); |
296 | 330 | maps[currentFloor]?.leaflet?.invalidateSize(); |
| 331 | + // Since the div containing the indoor map is saved after the first render |
| 332 | + // and later floors are shown or hidden instead of re-rendered, onReady is not |
| 333 | + // triggered again. Therefore we need to push the URL fragment manually |
| 334 | + // when switching floors. |
| 335 | + pushIndoorMapIdFragment(maps[currentFloor], locationId, floor); |
| 336 | + } |
| 337 | + |
| 338 | + function pushIndoorMapIdFragment(indoorMap, locationId, floor) { |
| 339 | + if (!indoorMap) { |
| 340 | + return; |
| 341 | + } |
| 342 | + const fragments = indoorMap?.utils?.parseUrlFragments(); |
| 343 | + const indoorMapId = indoorMap?.config?.bookmarkableActions?.id; |
| 344 | + if (!fragments || !indoorMapId) { |
| 345 | + return; |
| 346 | + } |
| 347 | + const indoorParams = fragments[indoorMapId] || new URLSearchParams(); |
| 348 | + if (!indoorParams.get("id")) { |
| 349 | + indoorParams.set("id", `${locationId}_${floor}`); |
| 350 | + } |
| 351 | + fragments[indoorMapId] = indoorParams; |
| 352 | + indoorMap?.utils?.updateUrlFragments(fragments); |
297 | 353 | } |
298 | 354 |
|
299 | 355 | let currentPopup = null; |
|
333 | 389 | .setLatLng(node?.properties.location) |
334 | 390 | .setContent(popupContent) |
335 | 391 | .openOn(map); |
| 392 | + currentPopup.on("remove", () => { |
| 393 | + const fragments = netjsongraphInstance.utils.parseUrlFragments(); |
| 394 | + const { id } = netjsongraphInstance.config.bookmarkableActions; |
| 395 | + if (fragments[id]) { |
| 396 | + fragments[id].delete("nodeId"); |
| 397 | + netjsongraphInstance.utils.updateUrlFragments(fragments); |
| 398 | + } |
| 399 | + currentPopup = null; |
| 400 | + }); |
336 | 401 | } |
337 | 402 |
|
338 | 403 | function renderIndoorMap(allResults, imageUrl, divId, floor) { |
|
363 | 428 | }, |
364 | 429 | bookmarkableActions: { |
365 | 430 | enabled: true, |
366 | | - id: `${locationId}:${floor}`, |
| 431 | + id: `${locationId}_${floor}`, |
367 | 432 | zoomOnRestore: false, |
368 | 433 | }, |
369 | 434 | nodeCategories: Object.keys(status_colors).map((status) => ({ |
|
469 | 534 | } |
470 | 535 | map.invalidateSize(); |
471 | 536 | }); |
| 537 | + // Push the indoor map fragment id=<locationId>:<floor> to the URL once the map |
| 538 | + // instance is ready, so the indoor map can be opened directly from the URL |
| 539 | + // without requiring a node click to add the fragment. |
| 540 | + pushIndoorMapIdFragment(this, locationId, floor); |
472 | 541 | }, |
473 | 542 | onClickElement: function (type, data) { |
474 | 543 | loadPopUpContent(data, this); |
|
0 commit comments