mirror of
https://github.com/MarekWo/mc-webui.git
synced 2026-07-27 20:12:41 +02:00
feat(pathanalyzer): map path styling and manual-pick undo
- Drawn paths now use red (#dc3545) for hop markers and polylines, clearly distinct from the purple base repeater dots (origin stays green, ambiguous candidates amber) - Resolved hops render as numbered badges matching the legend order, with a permanent name label next to each point (origin included) - Manual candidate assignments are reversible: a picked hop shows an undo icon in the legend, and a 'Reset picks' button clears every manual assignment on the current path Verified live via Playwright: line/marker colors, badge numbers and name labels, pick -> per-hop undo -> re-pick -> reset-all flow, no page errors. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -502,6 +502,19 @@ function paRenderStats() {
|
||||
let paMap = null;
|
||||
let paBaseLayer = null; // repeater contact markers
|
||||
let paPathLayer = null; // drawn path for the selected echo
|
||||
|
||||
// Path drawing color - distinct from the purple base markers so the
|
||||
// route stands out (origin stays green, ambiguous candidates amber)
|
||||
const PA_PATH_COLOR = '#dc3545';
|
||||
|
||||
function paHopIcon(n) {
|
||||
return L.divIcon({
|
||||
className: 'pa-hop-icon',
|
||||
html: `<div class="pa-hop-badge">${n}</div>`,
|
||||
iconSize: [22, 22],
|
||||
iconAnchor: [11, 11],
|
||||
});
|
||||
}
|
||||
let paMapSelection = { msgId: null, echoIdx: null };
|
||||
let paPicks = {}; // token -> public_key chosen by the user (collision disambiguation)
|
||||
|
||||
@@ -560,16 +573,19 @@ function paDrawSelectedEcho() {
|
||||
if (origin) {
|
||||
L.circleMarker([origin.adv_lat, origin.adv_lon], {
|
||||
radius: 7, color: '#198754', weight: 2, fillOpacity: 0.8
|
||||
}).bindPopup(`<strong>${origin.name}</strong><br>Origin (sender)`).addTo(paPathLayer);
|
||||
}).bindPopup(`<strong>${origin.name}</strong><br>Origin (sender)`)
|
||||
.bindTooltip(origin.name, { permanent: true, direction: 'right', offset: [8, 0], className: 'pa-hop-label' })
|
||||
.addTo(paPathLayer);
|
||||
points.push({ latlng: [origin.adv_lat, origin.adv_lon], gapBefore: false });
|
||||
}
|
||||
|
||||
echo.tokens.forEach((tok, i) => {
|
||||
const { contact, candidates } = paResolveHop(tok);
|
||||
if (contact) {
|
||||
L.circleMarker([contact.adv_lat, contact.adv_lon], {
|
||||
radius: 7, color: '#6f42c1', weight: 2, fillOpacity: 0.9
|
||||
}).bindPopup(`<strong>${contact.name}</strong><br>Hop ${i + 1}: <code>${tok}</code>`).addTo(paPathLayer);
|
||||
L.marker([contact.adv_lat, contact.adv_lon], { icon: paHopIcon(i + 1) })
|
||||
.bindPopup(`<strong>${contact.name}</strong><br>Hop ${i + 1}: <code>${tok}</code>`)
|
||||
.bindTooltip(contact.name, { permanent: true, direction: 'right', offset: [13, 0], className: 'pa-hop-label' })
|
||||
.addTo(paPathLayer);
|
||||
points.push({ latlng: [contact.adv_lat, contact.adv_lon], gapBefore: gapPending });
|
||||
gapPending = false;
|
||||
} else {
|
||||
@@ -586,7 +602,7 @@ function paDrawSelectedEcho() {
|
||||
// Polyline: solid between consecutive resolved hops, dashed across skipped ones
|
||||
for (let i = 1; i < points.length; i++) {
|
||||
L.polyline([points[i - 1].latlng, points[i].latlng], {
|
||||
color: '#6f42c1', weight: 3, opacity: 0.8,
|
||||
color: PA_PATH_COLOR, weight: 3, opacity: 0.85,
|
||||
dashArray: points[i].gapBefore ? '6 8' : null
|
||||
}).addTo(paPathLayer);
|
||||
}
|
||||
@@ -602,6 +618,24 @@ function paDrawSelectedEcho() {
|
||||
function paBuildLegend(echo) {
|
||||
const legend = document.createElement('div');
|
||||
legend.className = 'pa-legend';
|
||||
|
||||
// Path-level reset for manual candidate assignments
|
||||
if (echo.tokens.some(tok => paPicks[tok])) {
|
||||
const resetRow = document.createElement('div');
|
||||
resetRow.className = 'text-end';
|
||||
const resetBtn = document.createElement('button');
|
||||
resetBtn.className = 'btn btn-sm btn-outline-warning py-0 pa-reset-picks';
|
||||
resetBtn.innerHTML = '<i class="bi bi-arrow-counterclockwise"></i> Reset picks';
|
||||
resetBtn.title = 'Clear all manual repeater assignments on this path';
|
||||
resetBtn.addEventListener('click', (e) => {
|
||||
e.stopPropagation();
|
||||
echo.tokens.forEach(tok => { delete paPicks[tok]; });
|
||||
paRenderMapView();
|
||||
});
|
||||
resetRow.appendChild(resetBtn);
|
||||
legend.appendChild(resetRow);
|
||||
}
|
||||
|
||||
echo.tokens.forEach((tok, i) => {
|
||||
const row = document.createElement('div');
|
||||
row.className = 'pa-legend-hop';
|
||||
@@ -613,6 +647,18 @@ function paBuildLegend(echo) {
|
||||
const name = document.createElement('span');
|
||||
name.textContent = '— ' + contact.name;
|
||||
row.appendChild(name);
|
||||
if (paPicks[tok]) {
|
||||
// Manually assigned - allow undoing just this hop
|
||||
const undo = document.createElement('i');
|
||||
undo.className = 'bi bi-x-circle pa-unpick';
|
||||
undo.title = 'Undo this manual assignment';
|
||||
undo.addEventListener('click', (e) => {
|
||||
e.stopPropagation();
|
||||
delete paPicks[tok];
|
||||
paRenderMapView();
|
||||
});
|
||||
row.appendChild(undo);
|
||||
}
|
||||
} else if (candidates.length > 1) {
|
||||
const amb = document.createElement('span');
|
||||
amb.className = 'pa-ambiguous';
|
||||
|
||||
@@ -166,6 +166,43 @@
|
||||
color: #212529;
|
||||
}
|
||||
|
||||
.pa-unpick {
|
||||
cursor: pointer;
|
||||
color: var(--text-secondary, #6c757d);
|
||||
}
|
||||
|
||||
.pa-unpick:hover {
|
||||
color: #dc3545;
|
||||
}
|
||||
|
||||
.pa-reset-picks {
|
||||
font-size: 0.72rem;
|
||||
}
|
||||
|
||||
/* Numbered hop markers + name labels on the drawn path */
|
||||
.pa-hop-badge {
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
border-radius: 50%;
|
||||
background-color: #dc3545;
|
||||
color: #fff;
|
||||
border: 2px solid #fff;
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.4);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 0.7rem;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.pa-hop-label {
|
||||
font-size: 0.7rem;
|
||||
padding: 0 4px;
|
||||
background: rgba(255, 255, 255, 0.85);
|
||||
border: 1px solid rgba(0, 0, 0, 0.2);
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
/* Packet hash inside the expanded detail row - narrow screens only,
|
||||
where the Hash column is hidden */
|
||||
.pa-detail-hash {
|
||||
|
||||
Reference in New Issue
Block a user