mirror of
https://github.com/Kpa-clawbot/meshcore-analyzer.git
synced 2026-10-15 08:29:55 +00:00
feat: make "View Estimated Nodes on Map" a shareable/bookmarkable URL
dborup: clicking the link just landed on a bare "#/map" with no way to share it -- state lived in sessionStorage, gone after the click and never in the URL to begin with. Switched to a deep link, #/map?estimatedNodes=1, same convention as the existing packet-path deep link (#/map?packet=<hash>&obs=<id>, loadRouteFromDeepLink). map.js's new loadEstimatedNodesFromDeepLink fetches /api/analytics/areas fresh and draws the result -- no sessionStorage involved, since this list isn't per-request state, just the deployment's current estimate, so the URL alone reproduces it. Copy/share/reload all just work now. Also converted the trigger from a JS-click <button> to a real <a href> so "Copy Link Address" works without even clicking through. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
276daabfb7
commit
ded3255e08
+1
-9
@@ -6783,7 +6783,7 @@ function destroy() { _stopRolesRefresh(); _stopScopesRefresh(); _stopForeignTraf
|
||||
'<div class="analytics-card" style="margin-top:16px">' +
|
||||
'<h3 style="display:flex;align-items:center;justify-content:space-between;gap:8px;flex-wrap:wrap">' +
|
||||
'<span>Position-Fix Coverage Gaps by Area</span>' +
|
||||
(estimatedNodes.length ? '<button type="button" id="areasViewEstimatedNodes" class="btn-link" style="font-size:12px;font-weight:400;cursor:pointer;background:none;border:1px solid var(--border);border-radius:4px;padding:4px 10px;color:var(--link-color)">View Estimated Nodes on Map (' + estimatedNodes.length.toLocaleString() + ')</button>' : '') +
|
||||
(estimatedNodes.length ? '<a href="#/map?estimatedNodes=1" id="areasViewEstimatedNodes" class="btn-link" style="font-size:12px;font-weight:400;text-decoration:none;background:none;border:1px solid var(--border);border-radius:4px;padding:4px 10px;color:var(--link-color)">View Estimated Nodes on Map (' + estimatedNodes.length.toLocaleString() + ')</a>' : '') +
|
||||
'</h3>' +
|
||||
'<p class="text-muted" style="margin:0 0 8px;font-size:0.85em">How many of each area\'s nodes have an actual reported GPS position vs. how many were only placeable via a neighbor-based estimate (same technique used for View Path\'s approximate markers). Click a column header to sort by it.</p>' +
|
||||
'<div id="areasPositionGaps">' + gapsSection.tableHtml() + '</div>' +
|
||||
@@ -6793,14 +6793,6 @@ function destroy() { _stopRolesRefresh(); _stopScopesRefresh(); _stopForeignTraf
|
||||
densitySection.attach();
|
||||
bridgeSection.attach();
|
||||
gapsSection.attach();
|
||||
|
||||
var viewEstimatedBtn = document.getElementById('areasViewEstimatedNodes');
|
||||
if (viewEstimatedBtn) {
|
||||
viewEstimatedBtn.addEventListener('click', function () {
|
||||
sessionStorage.setItem('map-estimated-nodes', JSON.stringify({ points: estimatedNodes }));
|
||||
window.location.hash = '#/map';
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
el.innerHTML = '<div class="text-center" style="color:var(--status-red);padding:20px">Failed to load area analytics: ' + esc(String(e)) + '</div>';
|
||||
}
|
||||
|
||||
+40
-14
@@ -679,19 +679,15 @@
|
||||
} catch {}
|
||||
return;
|
||||
}
|
||||
// Check for an estimated-nodes view from the Areas tab (via
|
||||
// sessionStorage) — see drawEstimatedNodes. Checked before
|
||||
// map-route-hops since both use similar payload shapes but mean
|
||||
// different things (a resolved hop route vs. a flat point list).
|
||||
const estimatedNodesJson = sessionStorage.getItem('map-estimated-nodes');
|
||||
if (estimatedNodesJson) {
|
||||
sessionStorage.removeItem('map-estimated-nodes');
|
||||
try {
|
||||
const parsed = JSON.parse(estimatedNodesJson);
|
||||
if (parsed && Array.isArray(parsed.points)) {
|
||||
drawEstimatedNodes(parsed.points);
|
||||
}
|
||||
} catch {}
|
||||
// Estimated-nodes deep link: #/map?estimatedNodes=1 -- the Areas
|
||||
// tab's "View Estimated Nodes on Map" button links straight here
|
||||
// instead of stashing state in sessionStorage (like the old
|
||||
// map-estimated-nodes key did), so dborup can copy/share/bookmark
|
||||
// the URL and it still works after a reload -- same shareable-URL
|
||||
// convention as loadRouteFromDeepLink below (#1418/#1419).
|
||||
const hashQs = (location.hash || '').split('?')[1];
|
||||
if (new URLSearchParams(hashQs || '').get('estimatedNodes')) {
|
||||
loadEstimatedNodesFromDeepLink();
|
||||
return;
|
||||
}
|
||||
// Check for route from packet detail (via sessionStorage)
|
||||
@@ -1097,7 +1093,15 @@
|
||||
closeBtn.addTo(map);
|
||||
|
||||
const valid = (points || []).filter(function (p) { return p && p.lat != null && p.lon != null; });
|
||||
if (valid.length === 0) return;
|
||||
if (valid.length === 0) {
|
||||
const container = map.getContainer();
|
||||
const label = document.createElement('div');
|
||||
label.className = 'mc-estimated-nodes-label';
|
||||
label.style.cssText = 'position:absolute;top:10px;left:50px;z-index:1000;background:var(--input-bg,#1e293b);color:var(--text,#e2e8f0);padding:4px 10px;border-radius:4px;font-size:12px';
|
||||
label.textContent = 'No nodes currently have an estimated position.';
|
||||
container.appendChild(label);
|
||||
return;
|
||||
}
|
||||
|
||||
const color = '#f59e0b';
|
||||
valid.forEach(function (p) {
|
||||
@@ -1552,6 +1556,28 @@
|
||||
}
|
||||
}
|
||||
|
||||
// Deep link for #/map?estimatedNodes=1 -- fetches the current
|
||||
// network-wide estimated-position list fresh from the same endpoint
|
||||
// the Areas tab uses (/api/analytics/areas) and draws it, rather than
|
||||
// relying on a sessionStorage snapshot from whenever the link was
|
||||
// clicked. Since this list isn't per-user/per-request state (it's the
|
||||
// deployment's current estimate), the URL alone is enough to
|
||||
// reproduce the view -- share/bookmark/reload all just work.
|
||||
async function loadEstimatedNodesFromDeepLink() {
|
||||
try {
|
||||
const resp = await fetch('/api/analytics/areas');
|
||||
if (!resp.ok) {
|
||||
console.warn('[deep-link] /api/analytics/areas returned ' + resp.status);
|
||||
return;
|
||||
}
|
||||
const data = await resp.json();
|
||||
const points = (data && Array.isArray(data.estimatedNodes)) ? data.estimatedNodes : [];
|
||||
drawEstimatedNodes(points);
|
||||
} catch (e) {
|
||||
console.warn('[deep-link] estimated-nodes load failed', e);
|
||||
}
|
||||
}
|
||||
|
||||
async function loadNodes() {
|
||||
try {
|
||||
// Load regions from config + observed IATAs
|
||||
|
||||
@@ -331,19 +331,24 @@ function makeApiStub(resp) {
|
||||
assert.ok(!el.innerHTML.includes('Show all'), 'no toggle should render with only 1 area in positionGaps');
|
||||
});
|
||||
|
||||
await testAsync('renders a "View Estimated Nodes on Map" button with the estimated-node count when estimatedNodes is non-empty', async () => {
|
||||
await testAsync('renders a "View Estimated Nodes on Map" LINK (not a JS-click button) with the estimated-node count, pointing at a shareable/bookmarkable URL', async () => {
|
||||
const ctx = makeAnalyticsSandbox(makeApiStub(makeAreasResponse()));
|
||||
const el = fakeEl();
|
||||
await ctx.window._analyticsRenderAreasTab(el);
|
||||
assert.ok(el.innerHTML.includes('id="areasViewEstimatedNodes"'), 'the View Estimated Nodes button should render');
|
||||
assert.ok(el.innerHTML.includes('View Estimated Nodes on Map (1)'), 'the button label should show the estimated-node count');
|
||||
assert.ok(el.innerHTML.includes('id="areasViewEstimatedNodes"'), 'the View Estimated Nodes link should render');
|
||||
assert.ok(el.innerHTML.includes('View Estimated Nodes on Map (1)'), 'the link label should show the estimated-node count');
|
||||
// Must be a real <a href> so right-click "Copy Link Address" works and
|
||||
// the URL survives a reload -- dborup asked for this after the first
|
||||
// version (sessionStorage + a plain button) only ever led to a bare
|
||||
// "#/map" with no shareable state.
|
||||
assert.ok(el.innerHTML.includes('<a href="#/map?estimatedNodes=1" id="areasViewEstimatedNodes"'), 'expected a real anchor with the estimatedNodes=1 deep-link query param');
|
||||
});
|
||||
|
||||
await testAsync('does not render the "View Estimated Nodes on Map" button when estimatedNodes is empty', async () => {
|
||||
await testAsync('does not render the "View Estimated Nodes on Map" link when estimatedNodes is empty', async () => {
|
||||
const ctx = makeAnalyticsSandbox(makeApiStub(makeAreasResponse({ estimatedNodes: [] })));
|
||||
const el = fakeEl();
|
||||
await ctx.window._analyticsRenderAreasTab(el);
|
||||
assert.ok(!el.innerHTML.includes('id="areasViewEstimatedNodes"'), 'the button should not render when there are no estimated nodes to show');
|
||||
assert.ok(!el.innerHTML.includes('id="areasViewEstimatedNodes"'), 'the link should not render when there are no estimated nodes to show');
|
||||
});
|
||||
|
||||
await testAsync('renders the unpositioned-nodes summary note including the no-neighbor-fix subset', async () => {
|
||||
|
||||
Reference in New Issue
Block a user