feat(lab): phase 4 -- hash-router for sim deep-links
URL #sim/<name> deep-links: F5 restores sim, browser back/forward switches between sims, click on sim-card updates URL. 34 sims mapped via _SIM_HASH_MAP (built dynamically from SIMS array). Unknown hash -> console.warn fallback. Recursion guard prevents double-activation on programmatic navigate. closeSim clears hash via history.pushState (no hashchange loop). Embed mode excluded from hash updates (?embed=1 workflow unaffected). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -818,8 +818,71 @@
|
|||||||
} else {
|
} else {
|
||||||
renderSims();
|
renderSims();
|
||||||
if (_autoSim) openSim(_autoSim);
|
if (_autoSim) openSim(_autoSim);
|
||||||
|
// hash-router: activate sim from URL fragment after catalogue renders
|
||||||
|
else _activateFromHash();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
lucide.createIcons();
|
lucide.createIcons();
|
||||||
LS.notif.init();
|
LS.notif.init();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ─── Hash router for sim deep-links ─────────────────────────────────────
|
||||||
|
URL pattern: /lab#sim/<name>
|
||||||
|
<name> 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();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user