Have visualizer remember settings

This commit is contained in:
Jack Kingsman
2026-03-01 16:38:25 -08:00
parent e504f4de33
commit 18ac86b4c0
2 changed files with 137 additions and 12 deletions

View File

@@ -25,6 +25,7 @@ import {
type ContactAdvertPathSummary,
} from '../types';
import { getRawPacketObservationKey } from '../utils/rawPacketIdentity';
import { getVisualizerSettings, saveVisualizerSettings } from '../utils/visualizerSettings';
import { Checkbox } from './ui/checkbox';
import {
type NodeType,
@@ -34,7 +35,6 @@ import {
COLORS,
PARTICLE_COLOR_MAP,
PARTICLE_SPEED,
DEFAULT_OBSERVATION_WINDOW_SEC,
PACKET_LEGEND_ITEMS,
parsePacket,
getPacketLabel,
@@ -1007,19 +1007,55 @@ export function PacketVisualizer3D({
const mouseRef = useRef(new THREE.Vector2());
// Options
const [showAmbiguousPaths, setShowAmbiguousPaths] = useState(true);
const [showAmbiguousNodes, setShowAmbiguousNodes] = useState(false);
const [useAdvertPathHints, setUseAdvertPathHints] = useState(true);
const [splitAmbiguousByTraffic, setSplitAmbiguousByTraffic] = useState(true);
const [chargeStrength, setChargeStrength] = useState(-200);
const [observationWindowSec, setObservationWindowSec] = useState(DEFAULT_OBSERVATION_WINDOW_SEC);
const [letEmDrift, setLetEmDrift] = useState(true);
const [particleSpeedMultiplier, setParticleSpeedMultiplier] = useState(2);
const [showControls, setShowControls] = useState(true);
const [autoOrbit, setAutoOrbit] = useState(false);
const [pruneStaleNodes, setPruneStaleNodes] = useState(false);
const [savedSettings] = useState(getVisualizerSettings);
const [showAmbiguousPaths, setShowAmbiguousPaths] = useState(savedSettings.showAmbiguousPaths);
const [showAmbiguousNodes, setShowAmbiguousNodes] = useState(savedSettings.showAmbiguousNodes);
const [useAdvertPathHints, setUseAdvertPathHints] = useState(savedSettings.useAdvertPathHints);
const [splitAmbiguousByTraffic, setSplitAmbiguousByTraffic] = useState(
savedSettings.splitAmbiguousByTraffic
);
const [chargeStrength, setChargeStrength] = useState(savedSettings.chargeStrength);
const [observationWindowSec, setObservationWindowSec] = useState(
savedSettings.observationWindowSec
);
const [letEmDrift, setLetEmDrift] = useState(savedSettings.letEmDrift);
const [particleSpeedMultiplier, setParticleSpeedMultiplier] = useState(
savedSettings.particleSpeedMultiplier
);
const [showControls, setShowControls] = useState(savedSettings.showControls);
const [autoOrbit, setAutoOrbit] = useState(savedSettings.autoOrbit);
const [pruneStaleNodes, setPruneStaleNodes] = useState(savedSettings.pruneStaleNodes);
const [repeaterAdvertPaths, setRepeaterAdvertPaths] = useState<ContactAdvertPathSummary[]>([]);
// Persist visualizer controls to localStorage on change
useEffect(() => {
saveVisualizerSettings({
showAmbiguousPaths,
showAmbiguousNodes,
useAdvertPathHints,
splitAmbiguousByTraffic,
chargeStrength,
observationWindowSec,
letEmDrift,
particleSpeedMultiplier,
pruneStaleNodes,
autoOrbit,
showControls,
});
}, [
showAmbiguousPaths,
showAmbiguousNodes,
useAdvertPathHints,
splitAmbiguousByTraffic,
chargeStrength,
observationWindowSec,
letEmDrift,
particleSpeedMultiplier,
pruneStaleNodes,
autoOrbit,
showControls,
]);
useEffect(() => {
let cancelled = false;

View File

@@ -0,0 +1,89 @@
const VISUALIZER_SETTINGS_KEY = 'remoteterm-visualizer-settings';
export interface VisualizerSettings {
showAmbiguousPaths: boolean;
showAmbiguousNodes: boolean;
useAdvertPathHints: boolean;
splitAmbiguousByTraffic: boolean;
chargeStrength: number;
observationWindowSec: number;
letEmDrift: boolean;
particleSpeedMultiplier: number;
pruneStaleNodes: boolean;
autoOrbit: boolean;
showControls: boolean;
}
export const VISUALIZER_DEFAULTS: VisualizerSettings = {
showAmbiguousPaths: true,
showAmbiguousNodes: false,
useAdvertPathHints: true,
splitAmbiguousByTraffic: true,
chargeStrength: -200,
observationWindowSec: 15,
letEmDrift: true,
particleSpeedMultiplier: 2,
pruneStaleNodes: false,
autoOrbit: false,
showControls: true,
};
export function getVisualizerSettings(): VisualizerSettings {
try {
const raw = localStorage.getItem(VISUALIZER_SETTINGS_KEY);
if (!raw) return { ...VISUALIZER_DEFAULTS };
const parsed = JSON.parse(raw) as Partial<VisualizerSettings>;
return {
showAmbiguousPaths:
typeof parsed.showAmbiguousPaths === 'boolean'
? parsed.showAmbiguousPaths
: VISUALIZER_DEFAULTS.showAmbiguousPaths,
showAmbiguousNodes:
typeof parsed.showAmbiguousNodes === 'boolean'
? parsed.showAmbiguousNodes
: VISUALIZER_DEFAULTS.showAmbiguousNodes,
useAdvertPathHints:
typeof parsed.useAdvertPathHints === 'boolean'
? parsed.useAdvertPathHints
: VISUALIZER_DEFAULTS.useAdvertPathHints,
splitAmbiguousByTraffic:
typeof parsed.splitAmbiguousByTraffic === 'boolean'
? parsed.splitAmbiguousByTraffic
: VISUALIZER_DEFAULTS.splitAmbiguousByTraffic,
chargeStrength:
typeof parsed.chargeStrength === 'number'
? parsed.chargeStrength
: VISUALIZER_DEFAULTS.chargeStrength,
observationWindowSec:
typeof parsed.observationWindowSec === 'number'
? parsed.observationWindowSec
: VISUALIZER_DEFAULTS.observationWindowSec,
letEmDrift:
typeof parsed.letEmDrift === 'boolean' ? parsed.letEmDrift : VISUALIZER_DEFAULTS.letEmDrift,
particleSpeedMultiplier:
typeof parsed.particleSpeedMultiplier === 'number'
? parsed.particleSpeedMultiplier
: VISUALIZER_DEFAULTS.particleSpeedMultiplier,
pruneStaleNodes:
typeof parsed.pruneStaleNodes === 'boolean'
? parsed.pruneStaleNodes
: VISUALIZER_DEFAULTS.pruneStaleNodes,
autoOrbit:
typeof parsed.autoOrbit === 'boolean' ? parsed.autoOrbit : VISUALIZER_DEFAULTS.autoOrbit,
showControls:
typeof parsed.showControls === 'boolean'
? parsed.showControls
: VISUALIZER_DEFAULTS.showControls,
};
} catch {
return { ...VISUALIZER_DEFAULTS };
}
}
export function saveVisualizerSettings(settings: VisualizerSettings): void {
try {
localStorage.setItem(VISUALIZER_SETTINGS_KEY, JSON.stringify(settings));
} catch {
// localStorage may be unavailable
}
}