Add observation-level deeplinks to packet detail page

When viewing a specific observation, the URL now includes ?obs=OBSERVER_ID.
Opening such a link auto-expands the group and selects the observation.
Copy Link button includes the obs parameter when an observation is selected.
This commit is contained in:
you
2026-03-21 22:59:07 +00:00
parent 2894c38435
commit 1b2f28cb5f
2 changed files with 37 additions and 7 deletions
+1 -1
View File
@@ -84,7 +84,7 @@
<script src="hop-resolver.js?v=1774126708"></script>
<script src="app.js?v=1774126708"></script>
<script src="home.js?v=1774042199"></script>
<script src="packets.js?v=1774166400"></script>
<script src="packets.js?v=1774339200"></script>
<script src="map.js?v=1774126708" onerror="console.error('Failed to load:', this.src)"></script>
<script src="channels.js?v=1774331200" onerror="console.error('Failed to load:', this.src)"></script>
<script src="nodes.js?v=1774126708" onerror="console.error('Failed to load:', this.src)"></script>
+36 -6
View File
@@ -136,8 +136,17 @@
let directPacketHash = null;
let initGeneration = 0;
let directObsId = null;
async function init(app, routeParam) {
const gen = ++initGeneration;
// Parse ?obs=OBSERVER_ID from routeParam
if (routeParam && routeParam.includes('?')) {
const qIdx = routeParam.indexOf('?');
const qs = new URLSearchParams(routeParam.substring(qIdx));
directObsId = qs.get('obs');
routeParam = routeParam.substring(0, qIdx);
}
// Detect route param type: "id/123" for direct packet, short hex for hash, long hex for node
if (routeParam) {
if (routeParam.startsWith('id/')) {
@@ -163,11 +172,27 @@
// Auto-select packet detail when arriving via hash URL
if (directPacketHash) {
const h = directPacketHash;
const obsTarget = directObsId;
directPacketHash = null;
directObsId = null;
try {
const data = await api(`/packets/${h}`);
if (gen === initGeneration && data?.packet) {
selectPacket(data.packet.id, h);
if (obsTarget && data.observations) {
// Find the matching observation and select it
const obs = data.observations.find(o => String(o.observer_id) === String(obsTarget));
if (obs) {
// Expand the group so the observation row is visible
expandedHashes.add(h);
// Build the obs-specific packet view
const obsPacket = {...data.packet, observer_id: obs.observer_id, observer_name: obs.observer_name, snr: obs.snr, rssi: obs.rssi, path_json: obs.path_json, timestamp: obs.timestamp, first_seen: obs.timestamp};
selectPacket(obs.id || data.packet.id, h, {packet: obsPacket, breakdown: data.breakdown, observations: data.observations}, obs.observer_id);
} else {
selectPacket(data.packet.id, h, data);
}
} else {
selectPacket(data.packet.id, h, data);
}
}
} catch {}
}
@@ -588,7 +613,7 @@
if (child) {
const parentData = group._fetchedData;
const obsPacket = parentData ? {...parentData.packet, observer_id: child.observer_id, observer_name: child.observer_name, snr: child.snr, rssi: child.rssi, path_json: child.path_json, timestamp: child.timestamp, first_seen: child.timestamp} : child;
selectPacket(child.id, parentHash, {packet: obsPacket, breakdown: parentData?.breakdown, observations: parentData?.observations});
selectPacket(child.id, parentHash, {packet: obsPacket, breakdown: parentData?.breakdown, observations: parentData?.observations}, child.observer_id);
}
}
else if (action === 'select-hash') pktSelectHash(value);
@@ -753,12 +778,16 @@
return '';
}
async function selectPacket(id, hash, prefetchedData) {
let selectedObserverId = null;
async function selectPacket(id, hash, prefetchedData, obsId) {
selectedId = id;
selectedObserverId = obsId || null;
const obsParam = selectedObserverId ? `?obs=${selectedObserverId}` : '';
if (hash) {
history.replaceState(null, '', `#/packets/${hash}`);
history.replaceState(null, '', `#/packets/${hash}${obsParam}`);
} else {
history.replaceState(null, '', `#/packets/${id}`);
history.replaceState(null, '', `#/packets/${id}${obsParam}`);
}
renderTableRows();
const isMobileNow = window.innerWidth <= 640;
@@ -895,7 +924,8 @@
if (copyLinkBtn) {
copyLinkBtn.addEventListener('click', () => {
const pktHash = copyLinkBtn.dataset.packetHash;
const url = pktHash ? `${location.origin}/#/packets/${pktHash}` : `${location.origin}/#/packets/${copyLinkBtn.dataset.packetId}`;
const obsParam = selectedObserverId ? `?obs=${selectedObserverId}` : '';
const url = pktHash ? `${location.origin}/#/packets/${pktHash}${obsParam}` : `${location.origin}/#/packets/${copyLinkBtn.dataset.packetId}${obsParam}`;
navigator.clipboard.writeText(url).then(() => {
copyLinkBtn.textContent = '✅ Copied!';
setTimeout(() => { copyLinkBtn.textContent = '🔗 Copy Link'; }, 1500);