mirror of
https://github.com/jkingsman/Remote-Terminal-for-MeshCore.git
synced 2026-08-06 16:53:38 +02:00
Add custom pathing (closes #45)
This commit is contained in:
@@ -1,10 +1,13 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { type ReactNode, useEffect, useState } from 'react';
|
||||
import { api } from '../api';
|
||||
import { formatTime } from '../utils/messageParser';
|
||||
import {
|
||||
isValidLocation,
|
||||
calculateDistance,
|
||||
formatDistance,
|
||||
formatRouteLabel,
|
||||
getEffectiveContactRoute,
|
||||
hasRoutingOverride,
|
||||
parsePathHops,
|
||||
} from '../utils/pathUtils';
|
||||
import { getMapFocusHash } from '../utils/urlHash';
|
||||
@@ -106,7 +109,12 @@ export function ContactInfoPane({
|
||||
isValidLocation(contact.lat, contact.lon)
|
||||
? calculateDistance(config.lat, config.lon, contact.lat, contact.lon)
|
||||
: null;
|
||||
const pathHashModeLabel = contact ? formatPathHashMode(contact.out_path_hash_mode) : null;
|
||||
const effectiveRoute = contact ? getEffectiveContactRoute(contact) : null;
|
||||
const pathHashModeLabel =
|
||||
effectiveRoute && effectiveRoute.pathLen >= 0
|
||||
? formatPathHashMode(effectiveRoute.pathHashMode)
|
||||
: null;
|
||||
const learnedRouteLabel = contact ? formatRouteLabel(contact.last_path_len, true) : null;
|
||||
|
||||
return (
|
||||
<Sheet open={contactKey !== null} onOpenChange={(open) => !open && onClose()}>
|
||||
@@ -220,17 +228,24 @@ export function ContactInfoPane({
|
||||
{distFromUs !== null && (
|
||||
<InfoItem label="Distance" value={formatDistance(distFromUs)} />
|
||||
)}
|
||||
{contact.last_path_len >= 0 && (
|
||||
{effectiveRoute && (
|
||||
<InfoItem
|
||||
label="Hops"
|
||||
label="Routing"
|
||||
value={
|
||||
contact.last_path_len === 0
|
||||
? 'Direct'
|
||||
: `${contact.last_path_len} hop${contact.last_path_len > 1 ? 's' : ''}`
|
||||
effectiveRoute.forced ? (
|
||||
<span>
|
||||
{formatRouteLabel(effectiveRoute.pathLen, true)}{' '}
|
||||
<span className="text-destructive">(forced)</span>
|
||||
</span>
|
||||
) : (
|
||||
formatRouteLabel(effectiveRoute.pathLen, true)
|
||||
)
|
||||
}
|
||||
/>
|
||||
)}
|
||||
{contact.last_path_len === -1 && <InfoItem label="Routing" value="Flood" />}
|
||||
{contact && hasRoutingOverride(contact) && learnedRouteLabel && (
|
||||
<InfoItem label="Learned Route" value={learnedRouteLabel} />
|
||||
)}
|
||||
{pathHashModeLabel && <InfoItem label="Hop Width" value={pathHashModeLabel} />}
|
||||
</div>
|
||||
</div>
|
||||
@@ -468,7 +483,7 @@ function ChannelAttributionWarning() {
|
||||
);
|
||||
}
|
||||
|
||||
function InfoItem({ label, value }: { label: string; value: string }) {
|
||||
function InfoItem({ label, value }: { label: string; value: ReactNode }) {
|
||||
return (
|
||||
<div>
|
||||
<span className="text-muted-foreground text-xs">{label}</span>
|
||||
|
||||
@@ -2,7 +2,14 @@ import type { ReactNode } from 'react';
|
||||
import { toast } from './ui/sonner';
|
||||
import { api } from '../api';
|
||||
import { formatTime } from '../utils/messageParser';
|
||||
import { isValidLocation, calculateDistance, formatDistance } from '../utils/pathUtils';
|
||||
import {
|
||||
isValidLocation,
|
||||
calculateDistance,
|
||||
formatDistance,
|
||||
formatRouteLabel,
|
||||
formatRoutingOverrideInput,
|
||||
getEffectiveContactRoute,
|
||||
} from '../utils/pathUtils';
|
||||
import { getMapFocusHash } from '../utils/urlHash';
|
||||
import { handleKeyboardActivate } from '../utils/a11y';
|
||||
import type { Contact } from '../types';
|
||||
@@ -19,58 +26,48 @@ interface ContactStatusInfoProps {
|
||||
*/
|
||||
export function ContactStatusInfo({ contact, ourLat, ourLon }: ContactStatusInfoProps) {
|
||||
const parts: ReactNode[] = [];
|
||||
const effectiveRoute = getEffectiveContactRoute(contact);
|
||||
|
||||
const editRoutingOverride = () => {
|
||||
const route = window.prompt(
|
||||
'Enter explicit path as comma-separated 1, 2, or 3 byte hops (for example "ae,f1" or "ae92,f13e").\nEnter 0 to force direct always.\nEnter -1 to force flooding always.\nLeave blank to clear the override and reset to flood until a new path is heard.',
|
||||
formatRoutingOverrideInput(contact)
|
||||
);
|
||||
if (route === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
api.setContactRoutingOverride(contact.public_key, route).then(
|
||||
() =>
|
||||
toast.success(
|
||||
route.trim() === '' ? 'Routing override cleared' : 'Routing override updated'
|
||||
),
|
||||
(err: unknown) =>
|
||||
toast.error(err instanceof Error ? err.message : 'Failed to update routing override')
|
||||
);
|
||||
};
|
||||
|
||||
if (contact.last_seen) {
|
||||
parts.push(`Last heard: ${formatTime(contact.last_seen)}`);
|
||||
}
|
||||
|
||||
if (contact.last_path_len === -1) {
|
||||
parts.push('flood');
|
||||
} else if (contact.last_path_len === 0) {
|
||||
parts.push(
|
||||
<span
|
||||
key="path"
|
||||
className="cursor-pointer hover:text-primary hover:underline"
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
onKeyDown={handleKeyboardActivate}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
if (window.confirm('Reset path to flood?')) {
|
||||
api.resetContactPath(contact.public_key).then(
|
||||
() => toast.success('Path reset to flood'),
|
||||
() => toast.error('Failed to reset path')
|
||||
);
|
||||
}
|
||||
}}
|
||||
title="Click to reset path to flood"
|
||||
>
|
||||
direct
|
||||
</span>
|
||||
);
|
||||
} else if (contact.last_path_len > 0) {
|
||||
parts.push(
|
||||
<span
|
||||
key="path"
|
||||
className="cursor-pointer hover:text-primary hover:underline"
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
onKeyDown={handleKeyboardActivate}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
if (window.confirm('Reset path to flood?')) {
|
||||
api.resetContactPath(contact.public_key).then(
|
||||
() => toast.success('Path reset to flood'),
|
||||
() => toast.error('Failed to reset path')
|
||||
);
|
||||
}
|
||||
}}
|
||||
title="Click to reset path to flood"
|
||||
>
|
||||
{contact.last_path_len} hop{contact.last_path_len > 1 ? 's' : ''}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
parts.push(
|
||||
<span
|
||||
key="path"
|
||||
className="cursor-pointer hover:text-primary hover:underline"
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
onKeyDown={handleKeyboardActivate}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
editRoutingOverride();
|
||||
}}
|
||||
title="Click to edit routing override"
|
||||
>
|
||||
{formatRouteLabel(effectiveRoute.pathLen)}
|
||||
{effectiveRoute.forced && <span className="text-destructive"> (forced)</span>}
|
||||
</span>
|
||||
);
|
||||
|
||||
if (isValidLocation(contact.lat, contact.lon)) {
|
||||
const distFromUs =
|
||||
|
||||
Reference in New Issue
Block a user