Skip to content

Commit 7c81439

Browse files
committed
[fix] Switching floor from url
1 parent 50ca17c commit 7c81439

1 file changed

Lines changed: 99 additions & 18 deletions

File tree

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

Lines changed: 99 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -276,7 +276,7 @@
276276
);
277277
}
278278

279-
async function showFloor(url, floor) {
279+
async function showFloor(url, floor, { updateUrl = true } = {}) {
280280
const floorplanState = getFloorplanState();
281281
if (!floorplanState?.state) return;
282282

@@ -313,7 +313,14 @@
313313
// and later floors are shown or hidden instead of re-rendered, onReady is not
314314
// triggered again. Therefore we need to push the URL fragment manually
315315
// when switching floors.
316-
pushIndoorMapIdFragment(nextState.maps[nextState.state.currentFloor], floor);
316+
if (updateUrl) {
317+
// Use pushState so Back/Forward navigates across floor changes.
318+
pushIndoorMapIdFragment(
319+
nextState.maps[nextState.state.currentFloor],
320+
floor,
321+
false,
322+
);
323+
}
317324
}
318325

319326
function pushIndoorMapIdFragment(indoorMap, floor, replace = true) {
@@ -323,13 +330,23 @@
323330
const floorplanState = getFloorplanState();
324331
if (!floorplanState?.state) return;
325332
const fragments = indoorMap?.utils?.parseUrlFragments();
326-
const indoorMapId = indoorMap?.config?.bookmarkableActions?.id;
327-
if (!fragments || !indoorMapId) {
333+
if (!fragments) {
328334
return;
329335
}
330-
const indoorParams = fragments[indoorMapId] || new URLSearchParams();
331-
indoorParams.set("id", `${floorplanState.state.locationId}_${floor}`);
332-
fragments[indoorMapId] = indoorParams;
336+
const geoMapId = "dashboard-geo-map";
337+
const fragmentId = `${floorplanState.state.locationId}_${floor}`;
338+
339+
// Ensure we never accumulate multiple indoor fragments when switching floors.
340+
// Keep the geo map fragment and replace any existing indoor fragment(s).
341+
Object.keys(fragments).forEach((key) => {
342+
if (key !== geoMapId && key !== fragmentId) {
343+
delete fragments[key];
344+
}
345+
});
346+
347+
const indoorParams = fragments[fragmentId] || new URLSearchParams();
348+
indoorParams.set("id", fragmentId);
349+
fragments[fragmentId] = indoorParams;
333350
indoorMap?.utils?.updateUrlFragments(fragments, null, replace);
334351
}
335352

@@ -533,7 +550,19 @@
533550
// instance is ready, so the indoor map can be opened directly from the URL
534551
// without requiring a node click to add the fragment.
535552
if (isActiveFloor) {
536-
pushIndoorMapIdFragment(this, floor);
553+
// Use pushState the first time we add the indoor fragment so Back
554+
// returns to the geo-map-only state (with popup restored)
555+
// If the fragment for this exact floor is already present (page-load
556+
// restore or history navigation), use replaceState to avoid creating
557+
// duplicate entries.
558+
// Otherwise (initial open or floor switch), use pushState so Back/Forward
559+
// can navigate across floor changes.
560+
const fragments = this.utils?.parseUrlFragments?.();
561+
const indoorMapId = this.config?.bookmarkableActions?.id;
562+
const hasThisFloorFragment = Boolean(
563+
fragments && indoorMapId && fragments[indoorMapId],
564+
);
565+
pushIndoorMapIdFragment(this, floor, hasThisFloorFragment);
537566
}
538567
},
539568
// Popup handling is delegated to nodePopup.content,
@@ -594,6 +623,27 @@
594623
await showFloor(floorplanState.state.url, floorplanState.state.currentFloor);
595624
}
596625

626+
async function navigateToFloorFromUrl(newIndex) {
627+
// Like navigateToFloor, but avoids pushing a new history entry.
628+
const floorplanState = getFloorplanState();
629+
if (!floorplanState?.state) return;
630+
floorplanState.selectedIndex = newIndex;
631+
const maxStart = Math.max(0, floorplanState.floors.length - NAV_WINDOW_SIZE);
632+
const center = Math.floor(NAV_WINDOW_SIZE / 2);
633+
floorplanState.navWindowStart = Math.max(
634+
0,
635+
Math.min(floorplanState.selectedIndex - center, maxStart),
636+
);
637+
addFloorButtons();
638+
floorplanState.state.currentFloor =
639+
floorplanState.floors[floorplanState.selectedIndex];
640+
setFloorplanState(floorplanState);
641+
$(".floorplan-loading-spinner").show();
642+
await showFloor(floorplanState.state.url, floorplanState.state.currentFloor, {
643+
updateUrl: false,
644+
});
645+
}
646+
597647
// Delegated event handlers (set up once at module init)
598648
$(document).on("click", "#floorplan-close-btn", destroyFloorplan);
599649

@@ -638,18 +688,49 @@
638688
window.addEventListener("fragmentchange", () => {
639689
const indoorMapId = getIndoorMapIdFromUrl();
640690
const isOverlayOpen = document.getElementById("floorplan-overlay") !== null;
641-
if (indoorMapId) {
642-
if (!isOverlayOpen) {
643-
const { fragmentLocationId, fragmentFloor } = indoorMapId;
644-
const floorplanUrl = window._owGeoMapConfig.indoorCoordinatesUrl.replace(
645-
"000",
646-
fragmentLocationId,
647-
);
648-
openFloorPlan(floorplanUrl, fragmentLocationId, fragmentFloor);
691+
692+
// No indoor fragment → close overlay if open, then bail.
693+
if (!indoorMapId) {
694+
// Overlay is open but URL has no indoor fragment → destroy.
695+
if (isOverlayOpen) {
696+
destroyFloorplan();
649697
}
650-
} else if (isOverlayOpen) {
651-
destroyFloorplan();
698+
return;
699+
}
700+
701+
const { fragmentLocationId, fragmentFloor } = indoorMapId;
702+
// Indoor fragment present but overlay not open → open it.
703+
if (!isOverlayOpen) {
704+
const floorplanUrl = window._owGeoMapConfig.indoorCoordinatesUrl.replace(
705+
"000",
706+
fragmentLocationId,
707+
);
708+
openFloorPlan(floorplanUrl, fragmentLocationId, fragmentFloor);
709+
return;
710+
}
711+
// Overlay already open: keep it in sync with the URL floor fragment.
712+
const floorplanState = getFloorplanState();
713+
// No floorplan state → bail.
714+
if (!floorplanState?.state) {
715+
return;
716+
}
717+
// Different location id → don't interfere with another location's overlay.
718+
if (String(fragmentLocationId) !== String(floorplanState.state.locationId)) {
719+
return;
720+
}
721+
const targetFloor = String(fragmentFloor);
722+
// Already showing the target floor → nothing to do.
723+
if (String(floorplanState.state.currentFloor) === targetFloor) {
724+
return;
725+
}
726+
const idx = floorplanState.floors.findIndex((f) => String(f) === targetFloor);
727+
// Target floor not in the available floors list → bail.
728+
if (idx === -1) {
729+
return;
652730
}
731+
// Popstate/history navigation already changed the URL.
732+
// Do not push a new history entry while syncing UI.
733+
navigateToFloorFromUrl(idx);
653734
});
654735

655736
window.openFloorPlan = openFloorPlan;

0 commit comments

Comments
 (0)