Skip to content

Commit 3f7d960

Browse files
committed
Merge remote-tracking branch 'upstream/master' into issues/720-theme-colors-from-openwisp-utils
2 parents 570b548 + a102dda commit 3f7d960

File tree

4 files changed

+419
-57
lines changed

4 files changed

+419
-57
lines changed

.github/workflows/ci.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ jobs:
8585
pip install -U ${{ matrix.django-version }}
8686
pip install -r requirements-test.txt
8787
pip install -U -I -e .
88+
pip install -U ${{ matrix.django-version }}
8889
sudo npm install -g prettier
8990
9091
- name: Start InfluxDB

openwisp_monitoring/device/static/monitoring/js/floorplan.js

Lines changed: 81 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
let maps = {};
1313
let locationId = null;
1414
let popstateHandler = null;
15+
let hashchangeHandler = null;
1516
const escapeHtml = function (text) {
1617
if (!text) return "";
1718
const div = document.createElement("div");
@@ -41,8 +42,8 @@
4142
}
4243
const params = new URLSearchParams(indoorFragment);
4344
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("_") || [];
4647
if (!fragmentLocationId || fragmentFloor == null) {
4748
return null;
4849
}
@@ -55,19 +56,14 @@
5556
"000",
5657
fragmentLocationId,
5758
);
58-
openFloorPlan(`${floorplanUrl}`, fragmentLocationId, fragmentFloor);
59+
openFloorPlan(floorplanUrl, fragmentLocationId, fragmentFloor);
5960
}
6061

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() {
6964
if (popstateHandler) {
7065
window.removeEventListener("popstate", popstateHandler);
66+
popstateHandler = null;
7167
}
7268
popstateHandler = () => {
7369
const indoorMapId = getIndoorMapIdFromUrl();
@@ -77,6 +73,44 @@
7773
}
7874
};
7975
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();
80114

81115
await fetchData(url, floor);
82116
const idx = floors.indexOf(currentFloor);
@@ -294,6 +328,28 @@
294328
}
295329
$floorDiv.show();
296330
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);
297353
}
298354

299355
let currentPopup = null;
@@ -333,6 +389,15 @@
333389
.setLatLng(node?.properties.location)
334390
.setContent(popupContent)
335391
.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+
});
336401
}
337402

338403
function renderIndoorMap(allResults, imageUrl, divId, floor) {
@@ -363,7 +428,7 @@
363428
},
364429
bookmarkableActions: {
365430
enabled: true,
366-
id: `${locationId}:${floor}`,
431+
id: `${locationId}_${floor}`,
367432
zoomOnRestore: false,
368433
},
369434
nodeCategories: Object.keys(status_colors).map((status) => ({
@@ -469,6 +534,10 @@
469534
}
470535
map.invalidateSize();
471536
});
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);
472541
},
473542
onClickElement: function (type, data) {
474543
loadPopUpContent(data, this);

openwisp_monitoring/device/static/monitoring/js/location-inline.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@
3636
return;
3737
}
3838

39-
const indoorMapId = `id=${locationId}:${floor}&nodeId=${deviceLocationId}`;
39+
const indoorMapId = `id=${locationId}_${floor}&nodeId=${deviceLocationId}`;
4040
const openIndoorDeviceBtn = `
4141
<div class="form-row field-indoor-view-button view-on-map-div">
4242
<div>

0 commit comments

Comments
 (0)