mirror of
https://github.com/jkingsman/Remote-Terminal-for-MeshCore.git
synced 2026-06-11 17:04:52 +02:00
Expand raw packet feed and have Hide UI option
This commit is contained in:
-542
File diff suppressed because one or more lines are too long
-1
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
+542
File diff suppressed because one or more lines are too long
+1
File diff suppressed because one or more lines are too long
Vendored
+2
-2
@@ -13,8 +13,8 @@
|
||||
<link rel="shortcut icon" href="/favicon.ico" />
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png" />
|
||||
<link rel="manifest" href="/site.webmanifest" />
|
||||
<script type="module" crossorigin src="/assets/index-D05AJxJe.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-DgiuGBKg.css">
|
||||
<script type="module" crossorigin src="/assets/index-qvx9bWcn.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-Ty42XaBr.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
|
||||
@@ -857,9 +857,11 @@ interface PacketVisualizerProps {
|
||||
packets: RawPacket[];
|
||||
contacts: Contact[];
|
||||
config: RadioConfig | null;
|
||||
fullScreen?: boolean;
|
||||
onFullScreenChange?: (fullScreen: boolean) => void;
|
||||
}
|
||||
|
||||
export function PacketVisualizer({ packets, contacts, config }: PacketVisualizerProps) {
|
||||
export function PacketVisualizer({ packets, contacts, config, fullScreen, onFullScreenChange }: PacketVisualizerProps) {
|
||||
const canvasRef = useRef<HTMLCanvasElement>(null);
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
const [dimensions, setDimensions] = useState({ width: 800, height: 600 });
|
||||
@@ -870,6 +872,7 @@ export function PacketVisualizer({ packets, contacts, config }: PacketVisualizer
|
||||
const [chargeStrength, setChargeStrength] = useState(-200);
|
||||
const [filterOldRepeaters, setFilterOldRepeaters] = useState(false);
|
||||
const [letEmDrift, setLetEmDrift] = useState(true);
|
||||
const [hideUI, setHideUI] = useState(false);
|
||||
|
||||
// Pan/zoom
|
||||
const [transform, setTransform] = useState({ x: 0, y: 0, scale: 1 });
|
||||
@@ -1057,100 +1060,121 @@ export function PacketVisualizer({ packets, contacts, config }: PacketVisualizer
|
||||
/>
|
||||
|
||||
{/* Legend */}
|
||||
<div className="absolute bottom-4 left-4 bg-background/80 backdrop-blur-sm rounded-lg p-3 text-xs border border-border">
|
||||
<div className="flex gap-6">
|
||||
<div className="flex flex-col gap-1.5">
|
||||
<div className="text-muted-foreground font-medium mb-1">Nodes</div>
|
||||
{LEGEND_ITEMS.map((item) => (
|
||||
<div key={item.label} className="flex items-center gap-2">
|
||||
<span className={item.size}>{item.emoji}</span>
|
||||
<span>{item.label}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<div className="flex flex-col gap-1.5">
|
||||
<div className="text-muted-foreground font-medium mb-1">Packets</div>
|
||||
{PACKET_LEGEND_ITEMS.map((item) => (
|
||||
<div key={item.label} className="flex items-center gap-2">
|
||||
<div
|
||||
className="w-5 h-5 rounded-full flex items-center justify-center text-[8px] font-bold text-white"
|
||||
style={{ backgroundColor: item.color }}
|
||||
>
|
||||
{item.label}
|
||||
{!hideUI && (
|
||||
<div className="absolute bottom-4 left-4 bg-background/80 backdrop-blur-sm rounded-lg p-3 text-xs border border-border">
|
||||
<div className="flex gap-6">
|
||||
<div className="flex flex-col gap-1.5">
|
||||
<div className="text-muted-foreground font-medium mb-1">Nodes</div>
|
||||
{LEGEND_ITEMS.map((item) => (
|
||||
<div key={item.label} className="flex items-center gap-2">
|
||||
<span className={item.size}>{item.emoji}</span>
|
||||
<span>{item.label}</span>
|
||||
</div>
|
||||
<span>{item.description}</span>
|
||||
</div>
|
||||
))}
|
||||
))}
|
||||
</div>
|
||||
<div className="flex flex-col gap-1.5">
|
||||
<div className="text-muted-foreground font-medium mb-1">Packets</div>
|
||||
{PACKET_LEGEND_ITEMS.map((item) => (
|
||||
<div key={item.label} className="flex items-center gap-2">
|
||||
<div
|
||||
className="w-5 h-5 rounded-full flex items-center justify-center text-[8px] font-bold text-white"
|
||||
style={{ backgroundColor: item.color }}
|
||||
>
|
||||
{item.label}
|
||||
</div>
|
||||
<span>{item.description}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Options */}
|
||||
<div className="absolute top-4 right-4 bg-background/80 backdrop-blur-sm rounded-lg p-3 text-xs border border-border">
|
||||
<div className="flex flex-col gap-2">
|
||||
<div>Nodes: {data.stats.nodes}</div>
|
||||
<div>Links: {data.stats.links}</div>
|
||||
<div className="text-muted-foreground">
|
||||
Processed: {data.stats.processed} | Animated: {data.stats.animated}
|
||||
</div>
|
||||
<div className="border-t border-border pt-2 mt-1 flex flex-col gap-2">
|
||||
<label className="flex items-center gap-2 cursor-pointer">
|
||||
<Checkbox
|
||||
checked={showAmbiguousPaths}
|
||||
onCheckedChange={(c) => setShowAmbiguousPaths(c === true)}
|
||||
/>
|
||||
<span title="Show placeholder nodes for repeaters when the 1-byte prefix matches multiple contacts">
|
||||
Ambiguous repeaters
|
||||
</span>
|
||||
</label>
|
||||
<label className="flex items-center gap-2 cursor-pointer">
|
||||
<Checkbox
|
||||
checked={showAmbiguousNodes}
|
||||
onCheckedChange={(c) => setShowAmbiguousNodes(c === true)}
|
||||
/>
|
||||
<span title="Show placeholder nodes for senders/recipients when only a 1-byte prefix is known">
|
||||
Ambiguous sender/recipient
|
||||
</span>
|
||||
</label>
|
||||
<label className="flex items-center gap-2 cursor-pointer">
|
||||
<Checkbox
|
||||
checked={filterOldRepeaters}
|
||||
onCheckedChange={(c) => setFilterOldRepeaters(c === true)}
|
||||
/>
|
||||
<span title="Hide repeaters not heard within the last 48 hours">
|
||||
Hide repeaters >48hrs heard
|
||||
</span>
|
||||
</label>
|
||||
<label className="flex items-center gap-2 cursor-pointer">
|
||||
<Checkbox checked={letEmDrift} onCheckedChange={(c) => setLetEmDrift(c === true)} />
|
||||
<span title="When enabled, the graph continuously reorganizes itself into a better layout">
|
||||
Let 'em drift
|
||||
</span>
|
||||
</label>
|
||||
<div className="flex flex-col gap-1 mt-1">
|
||||
<label
|
||||
className="text-muted-foreground"
|
||||
title="How strongly nodes repel each other. Higher values spread nodes out more."
|
||||
>
|
||||
Repulsion: {Math.abs(chargeStrength)}
|
||||
</label>
|
||||
<input
|
||||
type="range"
|
||||
min="50"
|
||||
max="2500"
|
||||
value={Math.abs(chargeStrength)}
|
||||
onChange={(e) => setChargeStrength(-parseInt(e.target.value))}
|
||||
className="w-full h-2 bg-border rounded-lg appearance-none cursor-pointer accent-primary"
|
||||
/>
|
||||
</div>
|
||||
<button
|
||||
onClick={data.randomizePositions}
|
||||
className="mt-2 px-3 py-1.5 bg-primary/20 hover:bg-primary/30 text-primary rounded text-xs transition-colors"
|
||||
title="Randomize node positions and let the simulation settle into a new layout"
|
||||
>
|
||||
Shuffle layout
|
||||
</button>
|
||||
</div>
|
||||
<label className="flex items-center gap-2 cursor-pointer">
|
||||
<Checkbox checked={hideUI} onCheckedChange={(c) => setHideUI(c === true)} />
|
||||
<span title="Hide legends and controls for a cleaner view">Hide UI</span>
|
||||
</label>
|
||||
{!hideUI && (
|
||||
<>
|
||||
<div className="border-t border-border pt-2 mt-1">
|
||||
<div>Nodes: {data.stats.nodes}</div>
|
||||
<div>Links: {data.stats.links}</div>
|
||||
<div className="text-muted-foreground">
|
||||
Processed: {data.stats.processed} | Animated: {data.stats.animated}
|
||||
</div>
|
||||
</div>
|
||||
<div className="border-t border-border pt-2 mt-1 flex flex-col gap-2">
|
||||
<label className="flex items-center gap-2 cursor-pointer">
|
||||
<Checkbox
|
||||
checked={showAmbiguousPaths}
|
||||
onCheckedChange={(c) => setShowAmbiguousPaths(c === true)}
|
||||
/>
|
||||
<span title="Show placeholder nodes for repeaters when the 1-byte prefix matches multiple contacts">
|
||||
Ambiguous repeaters
|
||||
</span>
|
||||
</label>
|
||||
<label className="flex items-center gap-2 cursor-pointer">
|
||||
<Checkbox
|
||||
checked={showAmbiguousNodes}
|
||||
onCheckedChange={(c) => setShowAmbiguousNodes(c === true)}
|
||||
/>
|
||||
<span title="Show placeholder nodes for senders/recipients when only a 1-byte prefix is known">
|
||||
Ambiguous sender/recipient
|
||||
</span>
|
||||
</label>
|
||||
<label className="flex items-center gap-2 cursor-pointer">
|
||||
<Checkbox
|
||||
checked={filterOldRepeaters}
|
||||
onCheckedChange={(c) => setFilterOldRepeaters(c === true)}
|
||||
/>
|
||||
<span title="Hide repeaters not heard within the last 48 hours">
|
||||
Hide repeaters >48hrs heard
|
||||
</span>
|
||||
</label>
|
||||
<label className="flex items-center gap-2 cursor-pointer">
|
||||
<Checkbox checked={letEmDrift} onCheckedChange={(c) => setLetEmDrift(c === true)} />
|
||||
<span title="When enabled, the graph continuously reorganizes itself into a better layout">
|
||||
Let 'em drift
|
||||
</span>
|
||||
</label>
|
||||
<div className="flex flex-col gap-1 mt-1">
|
||||
<label
|
||||
className="text-muted-foreground"
|
||||
title="How strongly nodes repel each other. Higher values spread nodes out more."
|
||||
>
|
||||
Repulsion: {Math.abs(chargeStrength)}
|
||||
</label>
|
||||
<input
|
||||
type="range"
|
||||
min="50"
|
||||
max="2500"
|
||||
value={Math.abs(chargeStrength)}
|
||||
onChange={(e) => setChargeStrength(-parseInt(e.target.value))}
|
||||
className="w-full h-2 bg-border rounded-lg appearance-none cursor-pointer accent-primary"
|
||||
/>
|
||||
</div>
|
||||
<button
|
||||
onClick={data.randomizePositions}
|
||||
className="mt-2 px-3 py-1.5 bg-primary/20 hover:bg-primary/30 text-primary rounded text-xs transition-colors"
|
||||
title="Randomize node positions and let the simulation settle into a new layout"
|
||||
>
|
||||
Shuffle layout
|
||||
</button>
|
||||
{onFullScreenChange && (
|
||||
<label className="flex items-center gap-2 cursor-pointer mt-2 pt-2 border-t border-border">
|
||||
<Checkbox
|
||||
checked={fullScreen}
|
||||
onCheckedChange={(c) => onFullScreenChange(c === true)}
|
||||
/>
|
||||
<span title="Hide the packet feed panel">Full screen</span>
|
||||
</label>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -3,7 +3,6 @@ import type { Contact, RawPacket, RadioConfig } from '../types';
|
||||
import { PacketVisualizer } from './PacketVisualizer';
|
||||
import { RawPacketList } from './RawPacketList';
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from './ui/tabs';
|
||||
import { Checkbox } from './ui/checkbox';
|
||||
import { cn } from '@/lib/utils';
|
||||
|
||||
interface VisualizerViewProps {
|
||||
@@ -20,14 +19,6 @@ export function VisualizerView({ packets, contacts, config }: VisualizerViewProp
|
||||
{/* Header */}
|
||||
<div className="flex justify-between items-center px-4 py-3 border-b border-border font-medium text-lg">
|
||||
<span>Mesh Visualizer</span>
|
||||
{/* Full screen toggle - only show on larger screens */}
|
||||
<label className="hidden md:flex items-center gap-2 text-sm font-normal cursor-pointer">
|
||||
<Checkbox
|
||||
checked={fullScreen}
|
||||
onCheckedChange={(checked) => setFullScreen(checked === true)}
|
||||
/>
|
||||
<span>Full screen</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
{/* Mobile: Tabbed interface */}
|
||||
@@ -55,14 +46,20 @@ export function VisualizerView({ packets, contacts, config }: VisualizerViewProp
|
||||
fullScreen ? 'flex-1' : 'flex-1 border-r border-border'
|
||||
)}
|
||||
>
|
||||
<PacketVisualizer packets={packets} contacts={contacts} config={config} />
|
||||
<PacketVisualizer
|
||||
packets={packets}
|
||||
contacts={contacts}
|
||||
config={config}
|
||||
fullScreen={fullScreen}
|
||||
onFullScreenChange={setFullScreen}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Packet feed panel - hidden when full screen */}
|
||||
<div
|
||||
className={cn(
|
||||
'overflow-hidden transition-all duration-200',
|
||||
fullScreen ? 'w-0' : 'w-[45rem] lg:w-[54rem]'
|
||||
fullScreen ? 'w-0' : 'w-[31rem] lg:w-[38rem]'
|
||||
)}
|
||||
>
|
||||
<div className="h-full flex flex-col">
|
||||
|
||||
Reference in New Issue
Block a user