diff --git a/frontend/js/labs/lab-glue.js b/frontend/js/labs/lab-glue.js index 7400516..1f12cb9 100644 --- a/frontend/js/labs/lab-glue.js +++ b/frontend/js/labs/lab-glue.js @@ -818,8 +818,71 @@ } else { renderSims(); if (_autoSim) openSim(_autoSim); + // hash-router: activate sim from URL fragment after catalogue renders + else _activateFromHash(); } }); lucide.createIcons(); LS.notif.init(); } + + /* ─── Hash router for sim deep-links ───────────────────────────────────── + URL pattern: /lab#sim/ + matches SIMS[i].id (e.g. 'projectile', 'graph', 'chemsandbox'). + F5 restores sim. Browser back/forward switches between sims. + Click on sim-card updates URL via wrapped openSim. + ──────────────────────────────────────────────────────────────────────── */ + + // Build valid-id set from SIMS catalogue (filters out "coming soon" entries) + const _SIM_HASH_MAP = {}; + SIMS.forEach(function(s) { if (s.id) { _SIM_HASH_MAP[s.id] = s.id; } }); + + var _routerNavigating = false; + + function _activateFromHash() { + var m = (location.hash || '').match(/^#sim\/([\w-]+)/); + if (!m) return false; + var simName = m[1]; + if (!_SIM_HASH_MAP[simName]) { + // eslint-disable-next-line no-console + window.console && window.console.warn('lab-router: unknown sim', simName); + return false; + } + openSim(simName); + return true; + } + + // Intercept openSim to push URL hash on user-initiated navigation + var _origOpenSim = openSim; + openSim = function(id) { + _origOpenSim(id); + if (!_routerNavigating && !_embedMode) { + var baseId = id.includes(':') ? id.split(':')[0] : id; + if (_SIM_HASH_MAP[baseId]) { + _routerNavigating = true; + location.hash = '#sim/' + baseId; + // use setTimeout so hashchange fires after flag is set + setTimeout(function() { _routerNavigating = false; }, 0); + } + } + }; + + // Intercept closeSim to clear hash when returning to home grid + var _origCloseSim = closeSim; + closeSim = function() { + _origCloseSim(); + if (!_embedMode) { + _routerNavigating = true; + history.pushState(null, '', location.pathname + location.search); + setTimeout(function() { _routerNavigating = false; }, 0); + } + }; + + // Browser back/forward navigation + window.addEventListener('hashchange', function() { + if (_routerNavigating) return; + var hasHash = _activateFromHash(); + if (!hasHash && document.getElementById('lab-sim').classList.contains('open')) { + _origCloseSim(); + } + });