mirror of
https://github.com/Kpa-clawbot/meshcore-analyzer.git
synced 2026-05-29 20:14:18 +00:00
Fix VCR scrubber rubber-band, compact replay button, fix channel links
VCR: playhead now stays where you drag it — updateTimelinePlayhead() skips during drag (VCR.dragging flag). Always update playhead visually via direct DOM during scrub instead of relying on buffer position calc. Packets: replay button compact, inline next to View Route button. Analytics: channel links now navigate to specific channel hash.
This commit is contained in:
+1
-1
@@ -566,7 +566,7 @@
|
||||
<table class="analytics-table">
|
||||
<thead><tr><th>Channel</th><th>Hash</th><th>Messages</th><th>Unique Senders</th><th>Last Activity</th><th>Decrypted</th></tr></thead>
|
||||
<tbody>
|
||||
${ch.channels.map(c => `<tr class="clickable-row" onclick="location.hash='#/channels'">
|
||||
${ch.channels.map(c => `<tr class="clickable-row" onclick="location.hash='#/channels?ch=${c.hash}'">
|
||||
<td><strong>${esc(c.name || 'Unknown')}</strong></td>
|
||||
<td class="mono">${c.hash}</td>
|
||||
<td>${c.messages}</td>
|
||||
|
||||
+17
-19
@@ -330,6 +330,7 @@
|
||||
}
|
||||
|
||||
function updateTimelinePlayhead() {
|
||||
if (VCR.dragging) return; // don't fight the user's drag
|
||||
const canvas = document.getElementById('vcrTimeline');
|
||||
if (!canvas) return;
|
||||
const ctx = canvas.getContext('2d');
|
||||
@@ -574,24 +575,22 @@
|
||||
timelineEl.addEventListener('mouseleave', () => { timeTooltip.classList.add('hidden'); });
|
||||
|
||||
// Drag scrubbing on timeline
|
||||
let dragging = false;
|
||||
VCR.dragging = false;
|
||||
function scrubToX(clientX, isFinal) {
|
||||
const rect = timelineEl.getBoundingClientRect();
|
||||
const pct = Math.max(0, Math.min(1, (clientX - rect.left) / rect.width));
|
||||
const now = Date.now();
|
||||
const targetTs = now - VCR.timelineScope + pct * VCR.timelineScope;
|
||||
|
||||
// Always move playhead visually during drag
|
||||
const playheadEl = document.getElementById('vcrPlayhead');
|
||||
if (playheadEl) {
|
||||
playheadEl.style.left = (pct * rect.width) + 'px';
|
||||
}
|
||||
|
||||
// If buffer is empty or target is before buffer start, fetch from DB on release
|
||||
if (VCR.buffer.length === 0 || targetTs < VCR.buffer[0].ts - 5000) {
|
||||
if (isFinal) {
|
||||
vcrRewind(now - targetTs);
|
||||
} else {
|
||||
// Update playhead position visually during drag even without buffer
|
||||
const playheadEl = document.getElementById('vcrPlayhead');
|
||||
if (playheadEl) {
|
||||
playheadEl.style.left = (pct * rect.width) + 'px';
|
||||
}
|
||||
}
|
||||
if (isFinal) vcrRewind(now - targetTs);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -605,38 +604,37 @@
|
||||
stopReplay();
|
||||
VCR.playhead = closest;
|
||||
vcrSetMode('REPLAY');
|
||||
updateTimelinePlayhead();
|
||||
updateVCRUI();
|
||||
}
|
||||
timelineEl.addEventListener('mousedown', (e) => {
|
||||
dragging = true;
|
||||
VCR.dragging = true;
|
||||
scrubToX(e.clientX, false);
|
||||
e.preventDefault();
|
||||
});
|
||||
document.addEventListener('mousemove', (e) => {
|
||||
if (!dragging) return;
|
||||
if (!VCR.dragging) return;
|
||||
scrubToX(e.clientX, false);
|
||||
});
|
||||
document.addEventListener('mouseup', (e) => {
|
||||
if (!dragging) return;
|
||||
dragging = false;
|
||||
if (!VCR.dragging) return;
|
||||
VCR.dragging = false;
|
||||
scrubToX(e.clientX, true);
|
||||
if (VCR.mode === 'REPLAY') startReplay();
|
||||
});
|
||||
// Touch support
|
||||
timelineEl.addEventListener('touchstart', (e) => {
|
||||
dragging = true;
|
||||
VCR.dragging = true;
|
||||
scrubToX(e.touches[0].clientX, false);
|
||||
e.preventDefault();
|
||||
}, { passive: false });
|
||||
timelineEl.addEventListener('touchmove', (e) => {
|
||||
if (!dragging) return;
|
||||
if (!VCR.dragging) return;
|
||||
scrubToX(e.touches[0].clientX, false);
|
||||
});
|
||||
timelineEl.addEventListener('touchend', (e) => {
|
||||
if (!dragging) return;
|
||||
if (!VCR.dragging) return;
|
||||
const lastTouch = e.changedTouches[0];
|
||||
dragging = false;
|
||||
VCR.dragging = false;
|
||||
scrubToX(lastTouch.clientX, true);
|
||||
if (VCR.mode === 'REPLAY') startReplay();
|
||||
});
|
||||
|
||||
+4
-2
@@ -474,8 +474,10 @@
|
||||
<dt>Timestamp</dt><dd>${pkt.timestamp}</dd>
|
||||
<dt>Path</dt><dd>${pathHops.length ? renderPath(pathHops) : '—'}</dd>
|
||||
</dl>
|
||||
${pathHops.length ? `<button class="detail-map-link" id="viewRouteBtn">🗺️ View route on map</button>` : ''}
|
||||
<button class="replay-live-btn" title="Replay this packet on the live map">▶ Replay on Live Map</button>
|
||||
<div class="detail-actions">
|
||||
${pathHops.length ? `<button class="detail-map-link" id="viewRouteBtn">🗺️ View route on map</button>` : ''}
|
||||
<button class="replay-live-btn" title="Replay this packet on the live map">▶ Replay</button>
|
||||
</div>
|
||||
|
||||
${hasRawHex ? `<div class="hex-legend">${buildHexLegend(ranges)}</div>
|
||||
<div class="hex-dump">${createColoredHexDump(pkt.raw_hex, ranges)}</div>` : ''}
|
||||
|
||||
+10
-9
@@ -1069,15 +1069,17 @@ button.ch-item.ch-item-encrypted .ch-badge { filter: grayscale(0.6); }
|
||||
[data-theme="dark"] .node-qr svg rect[fill="#000000"] { fill: var(--text); }
|
||||
|
||||
/* Replay on Live Map button in packet detail */
|
||||
.replay-live-btn {
|
||||
display: block;
|
||||
width: 100%;
|
||||
.detail-actions {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
margin-top: 12px;
|
||||
padding: 8px 16px;
|
||||
}
|
||||
.replay-live-btn {
|
||||
padding: 5px 12px;
|
||||
background: rgba(168, 85, 247, 0.15);
|
||||
border: 1px solid rgba(168, 85, 247, 0.3);
|
||||
color: #c084fc;
|
||||
font-size: 0.85rem;
|
||||
font-size: 0.78rem;
|
||||
font-weight: 600;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
@@ -1120,16 +1122,15 @@ button.ch-item.ch-item-encrypted .ch-badge { filter: grayscale(0.6); }
|
||||
|
||||
/* Detail map link */
|
||||
.detail-map-link {
|
||||
display: inline-block;
|
||||
margin: 8px 0;
|
||||
padding: 6px 12px;
|
||||
padding: 5px 12px;
|
||||
background: rgba(245, 158, 11, 0.12);
|
||||
border: 1px solid rgba(245, 158, 11, 0.25);
|
||||
color: #fbbf24;
|
||||
border-radius: 6px;
|
||||
font-size: 0.82rem;
|
||||
font-size: 0.78rem;
|
||||
font-weight: 600;
|
||||
text-decoration: none;
|
||||
cursor: pointer;
|
||||
transition: background 0.15s;
|
||||
}
|
||||
.detail-map-link:hover { background: rgba(245, 158, 11, 0.25); }
|
||||
|
||||
Reference in New Issue
Block a user